Detailed changes
@@ -9987,7 +9987,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",
@@ -202,7 +202,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()
@@ -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,
@@ -162,7 +162,7 @@ impl WrapMap {
{
let tab_snapshot = new_snapshot.tab_snapshot.clone();
let range = TabPoint::zero()..tab_snapshot.max_point();
- let edits = new_snapshot
+ edits = new_snapshot
.update(
tab_snapshot,
&[TabEdit {
@@ -63,6 +63,7 @@ use language::{
use lazy_static::lazy_static;
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use lsp::{DiagnosticSeverity, LanguageServerId};
+use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
use multi_buffer::ToOffsetUtf16;
pub use multi_buffer::{
@@ -406,133 +407,17 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(cx: &mut AppContext) {
init_settings(cx);
- // cx.register_action_type(Editor::new_file);
- // cx.register_action_type(Editor::new_file_in_direction);
- // cx.register_action_type(Editor::cancel);
- // cx.register_action_type(Editor::newline);
- // cx.register_action_type(Editor::newline_above);
- // cx.register_action_type(Editor::newline_below);
- // cx.register_action_type(Editor::backspace);
- // cx.register_action_type(Editor::delete);
- // cx.register_action_type(Editor::tab);
- // cx.register_action_type(Editor::tab_prev);
- // cx.register_action_type(Editor::indent);
- // cx.register_action_type(Editor::outdent);
- // cx.register_action_type(Editor::delete_line);
- // cx.register_action_type(Editor::join_lines);
- // cx.register_action_type(Editor::sort_lines_case_sensitive);
- // cx.register_action_type(Editor::sort_lines_case_insensitive);
- // cx.register_action_type(Editor::reverse_lines);
- // cx.register_action_type(Editor::shuffle_lines);
- // cx.register_action_type(Editor::convert_to_upper_case);
- // cx.register_action_type(Editor::convert_to_lower_case);
- // cx.register_action_type(Editor::convert_to_title_case);
- // cx.register_action_type(Editor::convert_to_snake_case);
- // cx.register_action_type(Editor::convert_to_kebab_case);
- // cx.register_action_type(Editor::convert_to_upper_camel_case);
- // cx.register_action_type(Editor::convert_to_lower_camel_case);
- // cx.register_action_type(Editor::delete_to_previous_word_start);
- // cx.register_action_type(Editor::delete_to_previous_subword_start);
- // cx.register_action_type(Editor::delete_to_next_word_end);
- // cx.register_action_type(Editor::delete_to_next_subword_end);
- // cx.register_action_type(Editor::delete_to_beginning_of_line);
- // cx.register_action_type(Editor::delete_to_end_of_line);
- // cx.register_action_type(Editor::cut_to_end_of_line);
- // cx.register_action_type(Editor::duplicate_line);
- // cx.register_action_type(Editor::move_line_up);
- // cx.register_action_type(Editor::move_line_down);
- // cx.register_action_type(Editor::transpose);
- // cx.register_action_type(Editor::cut);
- // cx.register_action_type(Editor::copy);
- // cx.register_action_type(Editor::paste);
- // cx.register_action_type(Editor::undo);
- // cx.register_action_type(Editor::redo);
- // cx.register_action_type(Editor::move_page_up);
- // cx.register_action_type::<MoveDown>();
- // cx.register_action_type(Editor::move_page_down);
- // cx.register_action_type(Editor::next_screen);
- // cx.register_action_type::<MoveLeft>();
- // cx.register_action_type::<MoveRight>();
- // cx.register_action_type(Editor::move_to_previous_word_start);
- // cx.register_action_type(Editor::move_to_previous_subword_start);
- // cx.register_action_type(Editor::move_to_next_word_end);
- // cx.register_action_type(Editor::move_to_next_subword_end);
- // cx.register_action_type(Editor::move_to_beginning_of_line);
- // cx.register_action_type(Editor::move_to_end_of_line);
- // cx.register_action_type(Editor::move_to_start_of_paragraph);
- // cx.register_action_type(Editor::move_to_end_of_paragraph);
- // cx.register_action_type(Editor::move_to_beginning);
- // cx.register_action_type(Editor::move_to_end);
- // cx.register_action_type(Editor::select_up);
- // cx.register_action_type(Editor::select_down);
- // cx.register_action_type(Editor::select_left);
- // cx.register_action_type(Editor::select_right);
- // cx.register_action_type(Editor::select_to_previous_word_start);
- // cx.register_action_type(Editor::select_to_previous_subword_start);
- // cx.register_action_type(Editor::select_to_next_word_end);
- // cx.register_action_type(Editor::select_to_next_subword_end);
- // cx.register_action_type(Editor::select_to_beginning_of_line);
- // cx.register_action_type(Editor::select_to_end_of_line);
- // cx.register_action_type(Editor::select_to_start_of_paragraph);
- // cx.register_action_type(Editor::select_to_end_of_paragraph);
- // cx.register_action_type(Editor::select_to_beginning);
- // cx.register_action_type(Editor::select_to_end);
- // cx.register_action_type(Editor::select_all);
- // cx.register_action_type(Editor::select_all_matches);
- // cx.register_action_type(Editor::select_line);
- // cx.register_action_type(Editor::split_selection_into_lines);
- // cx.register_action_type(Editor::add_selection_above);
- // cx.register_action_type(Editor::add_selection_below);
- // cx.register_action_type(Editor::select_next);
- // cx.register_action_type(Editor::select_previous);
- // cx.register_action_type(Editor::toggle_comments);
- // cx.register_action_type(Editor::select_larger_syntax_node);
- // cx.register_action_type(Editor::select_smaller_syntax_node);
- // cx.register_action_type(Editor::move_to_enclosing_bracket);
- // cx.register_action_type(Editor::undo_selection);
- // cx.register_action_type(Editor::redo_selection);
- // cx.register_action_type(Editor::go_to_diagnostic);
- // cx.register_action_type(Editor::go_to_prev_diagnostic);
- // cx.register_action_type(Editor::go_to_hunk);
- // cx.register_action_type(Editor::go_to_prev_hunk);
- // cx.register_action_type(Editor::go_to_definition);
- // cx.register_action_type(Editor::go_to_definition_split);
- // cx.register_action_type(Editor::go_to_type_definition);
- // cx.register_action_type(Editor::go_to_type_definition_split);
- // cx.register_action_type(Editor::fold);
- // cx.register_action_type(Editor::fold_at);
- // cx.register_action_type(Editor::unfold_lines);
- // cx.register_action_type(Editor::unfold_at);
- // cx.register_action_type(Editor::gutter_hover);
- // cx.register_action_type(Editor::fold_selected_ranges);
- // cx.register_action_type(Editor::show_completions);
- // cx.register_action_type(Editor::toggle_code_actions);
- // cx.register_action_type(Editor::open_excerpts);
- // cx.register_action_type(Editor::toggle_soft_wrap);
- // cx.register_action_type(Editor::toggle_inlay_hints);
- // cx.register_action_type(Editor::reveal_in_finder);
- // cx.register_action_type(Editor::copy_path);
- // cx.register_action_type(Editor::copy_relative_path);
- // cx.register_action_type(Editor::copy_highlight_json);
- // cx.add_async_action(Editor::format);
- // cx.register_action_type(Editor::restart_language_server);
- // cx.register_action_type(Editor::show_character_palette);
- // cx.add_async_action(Editor::confirm_completion);
- // cx.add_async_action(Editor::confirm_code_action);
- // cx.add_async_action(Editor::rename);
- // cx.add_async_action(Editor::confirm_rename);
- // cx.add_async_action(Editor::find_all_references);
- // cx.register_action_type(Editor::next_copilot_suggestion);
- // cx.register_action_type(Editor::previous_copilot_suggestion);
- // cx.register_action_type(Editor::copilot_suggest);
- // cx.register_action_type(Editor::context_menu_first);
- // cx.register_action_type(Editor::context_menu_prev);
- // cx.register_action_type(Editor::context_menu_next);
- // cx.register_action_type(Editor::context_menu_last);
workspace::register_project_item::<Editor>(cx);
workspace::register_followable_item::<Editor>(cx);
workspace::register_deserializable_item::<Editor>(cx);
+ cx.observe_new_views(
+ |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
+ workspace.register_action(Editor::new_file);
+ workspace.register_action(Editor::new_file_in_direction);
+ },
+ )
+ .detach();
}
trait InvalidationRegion {
@@ -620,8 +505,6 @@ pub struct Editor {
ime_transaction: Option<TransactionId>,
active_diagnostics: Option<ActiveDiagnosticGroup>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
- // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
- // override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<Model<Project>>,
collaboration_hub: Option<Box<dyn CollaborationHub>>,
blink_manager: Model<BlinkManager>,
@@ -635,7 +518,7 @@ pub struct Editor {
inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
nav_history: Option<ItemNavHistory>,
context_menu: RwLock<Option<ContextMenu>>,
- // mouse_context_menu: View<context_menu::ContextMenu>,
+ mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
next_completion_id: CompletionId,
available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
@@ -1729,21 +1612,11 @@ impl Editor {
// Self::new(EditorMode::Full, buffer, None, field_editor_style, cx)
// }
- // pub fn auto_height(
- // max_lines: usize,
- // field_editor_style: Option<Arc<GetFieldEditorTheme>>,
- // cx: &mut ViewContext<Self>,
- // ) -> Self {
- // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
- // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
- // Self::new(
- // EditorMode::AutoHeight { max_lines },
- // buffer,
- // None,
- // field_editor_style,
- // cx,
- // )
- // }
+ pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
+ let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+ let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+ Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
+ }
pub fn for_buffer(
buffer: Model<Buffer>,
@@ -1763,14 +1636,7 @@ impl Editor {
}
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
- let mut clone = Self::new(
- self.mode,
- self.buffer.clone(),
- self.project.clone(),
- // todo!
- // self.get_field_editor_theme.clone(),
- cx,
- );
+ let mut clone = Self::new(self.mode, self.buffer.clone(), self.project.clone(), cx);
self.display_map.update(cx, |display_map, cx| {
let snapshot = display_map.snapshot(cx);
clone.display_map.update(cx, |display_map, cx| {
@@ -1787,17 +1653,11 @@ impl Editor {
mode: EditorMode,
buffer: Model<MultiBuffer>,
project: Option<Model<Project>>,
- // todo!()
- // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
- // let editor_view_id = cx.view_id();
let style = cx.text_style();
let font_size = style.font_size.to_pixels(cx.rem_size());
let display_map = cx.build_model(|cx| {
- // todo!()
- // let settings = settings::get::<ThemeSettings>(cx);
- // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
});
@@ -1853,7 +1713,6 @@ impl Editor {
ime_transaction: Default::default(),
active_diagnostics: None,
soft_wrap_mode_override,
- // get_field_editor_theme,
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
project,
blink_manager: blink_manager.clone(),
@@ -1867,8 +1726,7 @@ impl Editor {
inlay_background_highlights: Default::default(),
nav_history: None,
context_menu: RwLock::new(None),
- // mouse_context_menu: cx
- // .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
+ mouse_context_menu: None,
completion_tasks: Default::default(),
next_completion_id: 0,
next_inlay_id: 0,
@@ -1877,7 +1735,6 @@ impl Editor {
document_highlights_task: Default::default(),
pending_rename: Default::default(),
searchable: true,
- // override_text_style: None,
cursor_shape: Default::default(),
autoindent_mode: Some(AutoindentMode::EachLine),
collapse_matches: false,
@@ -1995,25 +1852,25 @@ impl Editor {
}
}
- // pub fn new_file_in_direction(
- // workspace: &mut Workspace,
- // action: &workspace::NewFileInDirection,
- // cx: &mut ViewContext<Workspace>,
- // ) {
- // let project = workspace.project().clone();
- // if project.read(cx).is_remote() {
- // cx.propagate();
- // } else if let Some(buffer) = project
- // .update(cx, |project, cx| project.create_buffer("", None, cx))
- // .log_err()
- // {
- // workspace.split_item(
- // action.0,
- // Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
- // cx,
- // );
- // }
- // }
+ pub fn new_file_in_direction(
+ workspace: &mut Workspace,
+ action: &workspace::NewFileInDirection,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ let project = workspace.project().clone();
+ if project.read(cx).is_remote() {
+ cx.propagate();
+ } else if let Some(buffer) = project
+ .update(cx, |project, cx| project.create_buffer("", None, cx))
+ .log_err()
+ {
+ workspace.split_item(
+ action.0,
+ Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
+ cx,
+ );
+ }
+ }
pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {
self.buffer.read(cx).replica_id()
@@ -8369,6 +8226,18 @@ impl Editor {
cx.notify();
}
+ pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
+ let rem_size = cx.rem_size();
+ self.display_map.update(cx, |map, cx| {
+ map.set_font(
+ style.text.font(),
+ style.text.font_size.to_pixels(rem_size),
+ cx,
+ )
+ });
+ self.style = Some(style);
+ }
+
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
self.display_map
.update(cx, |map, cx| map.set_wrap_width(width, cx))
@@ -8791,62 +8660,56 @@ impl Editor {
// self.searchable
// }
- // fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {
- // let active_item = workspace.active_item(cx);
- // let editor_handle = if let Some(editor) = active_item
- // .as_ref()
- // .and_then(|item| item.act_as::<Self>(cx))
- // {
- // editor
- // } else {
- // cx.propagate();
- // return;
- // };
-
- // let editor = editor_handle.read(cx);
- // let buffer = editor.buffer.read(cx);
- // if buffer.is_singleton() {
- // cx.propagate();
- // return;
- // }
+ fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
+ let buffer = self.buffer.read(cx);
+ if buffer.is_singleton() {
+ cx.propagate();
+ return;
+ }
- // let mut new_selections_by_buffer = HashMap::default();
- // for selection in editor.selections.all::<usize>(cx) {
- // for (buffer, mut range, _) in
- // buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
- // {
- // if selection.reversed {
- // mem::swap(&mut range.start, &mut range.end);
- // }
- // new_selections_by_buffer
- // .entry(buffer)
- // .or_insert(Vec::new())
- // .push(range)
- // }
- // }
+ let Some(workspace) = self.workspace() else {
+ cx.propagate();
+ return;
+ };
- // editor_handle.update(cx, |editor, cx| {
- // editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx);
- // });
- // let pane = workspace.active_pane().clone();
- // pane.update(cx, |pane, _| pane.disable_history());
-
- // // We defer the pane interaction because we ourselves are a workspace item
- // // and activating a new item causes the pane to call a method on us reentrantly,
- // // which panics if we're on the stack.
- // cx.defer(move |workspace, cx| {
- // for (buffer, ranges) in new_selections_by_buffer.into_iter() {
- // let editor = workspace.open_project_item::<Self>(buffer, cx);
- // editor.update(cx, |editor, cx| {
- // editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
- // s.select_ranges(ranges);
- // });
- // });
- // }
-
- // pane.update(cx, |pane, _| pane.enable_history());
- // });
- // }
+ let mut new_selections_by_buffer = HashMap::default();
+ for selection in self.selections.all::<usize>(cx) {
+ for (buffer, mut range, _) in
+ buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
+ {
+ if selection.reversed {
+ mem::swap(&mut range.start, &mut range.end);
+ }
+ new_selections_by_buffer
+ .entry(buffer)
+ .or_insert(Vec::new())
+ .push(range)
+ }
+ }
+
+ self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
+
+ // We defer the pane interaction because we ourselves are a workspace item
+ // and activating a new item causes the pane to call a method on us reentrantly,
+ // which panics if we're on the stack.
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ let pane = workspace.active_pane().clone();
+ pane.update(cx, |pane, _| pane.disable_history());
+
+ for (buffer, ranges) in new_selections_by_buffer.into_iter() {
+ let editor = workspace.open_project_item::<Self>(buffer, cx);
+ editor.update(cx, |editor, cx| {
+ editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
+ s.select_ranges(ranges);
+ });
+ });
+ }
+
+ pane.update(cx, |pane, _| pane.enable_history());
+ })
+ });
+ }
fn jump(
&mut self,
@@ -9392,7 +9255,7 @@ impl Render for Editor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let settings = ThemeSettings::get_global(cx);
let text_style = match self.mode {
- EditorMode::SingleLine => TextStyle {
+ EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
@@ -9405,8 +9268,6 @@ impl Render for Editor {
white_space: WhiteSpace::Normal,
},
- EditorMode::AutoHeight { max_lines } => todo!(),
-
EditorMode::Full => TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
@@ -9441,106 +9302,6 @@ impl Render for Editor {
}
}
-// impl View for Editor {
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// let style = self.style(cx);
-// let font_changed = self.display_map.update(cx, |map, cx| {
-// map.set_fold_ellipses_color(style.folds.ellipses.text_color);
-// map.set_font_with_size(style.text.font_id, style.text.font_size, cx)
-// });
-
-// if font_changed {
-// cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
-// hide_hover(editor, cx);
-// hide_link_definition(editor, cx);
-// });
-// }
-
-// Stack::new()
-// .with_child(EditorElement::new(style.clone()))
-// .with_child(ChildView::new(&self.mouse_context_menu, cx))
-// .into_any()
-// }
-
-// fn ui_name() -> &'static str {
-// "Editor"
-// }
-
-// fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext<Self>) {
-// if cx.is_self_focused() {
-// let focused_event = EditorFocused(cx.handle());
-// cx.emit(Event::Focused);
-// cx.emit_global(focused_event);
-// }
-// if let Some(rename) = self.pending_rename.as_ref() {
-// cx.focus(&rename.editor);
-// } else if cx.is_self_focused() || !focused.is::<Editor>() {
-// if !self.focused {
-// self.blink_manager.update(cx, BlinkManager::enable);
-// }
-// self.focused = true;
-// self.buffer.update(cx, |buffer, cx| {
-// buffer.finalize_last_transaction(cx);
-// if self.leader_peer_id.is_none() {
-// buffer.set_active_selections(
-// &self.selections.disjoint_anchors(),
-// self.selections.line_mode,
-// self.cursor_shape,
-// cx,
-// );
-// }
-// });
-// }
-// }
-
-// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-// let blurred_event = EditorBlurred(cx.handle());
-// cx.emit_global(blurred_event);
-// self.focused = false;
-// self.blink_manager.update(cx, BlinkManager::disable);
-// self.buffer
-// .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
-// self.hide_context_menu(cx);
-// hide_hover(self, cx);
-// cx.emit(Event::Blurred);
-// cx.notify();
-// }
-
-// fn modifiers_changed(
-// &mut self,
-// event: &gpui::platform::ModifiersChangedEvent,
-// cx: &mut ViewContext<Self>,
-// ) -> bool {
-// let pending_selection = self.has_pending_selection();
-
-// if let Some(point) = &self.link_go_to_definition_state.last_trigger_point {
-// if event.cmd && !pending_selection {
-// let point = point.clone();
-// let snapshot = self.snapshot(cx);
-// let kind = point.definition_kind(event.shift);
-
-// show_link_definition(kind, self, point, snapshot, cx);
-// return false;
-// }
-// }
-
-// {
-// if self.link_go_to_definition_state.symbol_range.is_some()
-// || !self.link_go_to_definition_state.definitions.is_empty()
-// {
-// self.link_go_to_definition_state.symbol_range.take();
-// self.link_go_to_definition_state.definitions.clear();
-// cx.notify();
-// }
-
-// self.link_go_to_definition_state.task = None;
-
-// self.clear_highlights::<LinkGoToDefinitionState>(cx);
-// }
-
-// false
-// }
-
impl InputHandler for Editor {
fn text_for_range(
&mut self,
@@ -9787,72 +9548,6 @@ impl InputHandler for Editor {
}
}
-// fn build_style(
-// settings: &ThemeSettings,
-// get_field_editor_theme: Option<&GetFieldEditorTheme>,
-// override_text_style: Option<&OverrideTextStyle>,
-// cx: &mut AppContext,
-// ) -> EditorStyle {
-// let font_cache = cx.font_cache();
-// let line_height_scalar = settings.line_height();
-// let theme_id = settings.theme.meta.id;
-// let mut theme = settings.theme.editor.clone();
-// let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme {
-// let field_editor_theme = get_field_editor_theme(&settings.theme);
-// theme.text_color = field_editor_theme.text.color;
-// theme.selection = field_editor_theme.selection;
-// theme.background = field_editor_theme
-// .container
-// .background_color
-// .unwrap_or_default();
-// EditorStyle {
-// text: field_editor_theme.text,
-// placeholder_text: field_editor_theme.placeholder_text,
-// line_height_scalar,
-// theme,
-// theme_id,
-// }
-// } else {
-// todo!();
-// // let font_family_id = settings.buffer_font_family;
-// // let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
-// // let font_properties = Default::default();
-// // let font_id = font_cache
-// // .select_font(font_family_id, &font_properties)
-// // .unwrap();
-// // let font_size = settings.buffer_font_size(cx);
-// // EditorStyle {
-// // text: TextStyle {
-// // color: settings.theme.editor.text_color,
-// // font_family_name,
-// // font_family_id,
-// // font_id,
-// // font_size,
-// // font_properties,
-// // underline: Default::default(),
-// // soft_wrap: false,
-// // },
-// // placeholder_text: None,
-// // line_height_scalar,
-// // theme,
-// // theme_id,
-// // }
-// };
-
-// if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) {
-// if let Some(highlighted) = style
-// .text
-// .clone()
-// .highlight(highlight_style, font_cache)
-// .log_err()
-// {
-// style.text = highlighted;
-// }
-// }
-
-// style
-// }
-
trait SelectionExt {
fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;
@@ -9,9 +9,11 @@ use crate::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
link_go_to_definition::{
- go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
- update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+ go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition,
+ update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
+ LinkGoToDefinitionState,
},
+ mouse_context_menu,
scroll::scroll_amount::ScrollAmount,
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point,
@@ -19,14 +21,15 @@ use crate::{
};
use anyhow::Result;
use collections::{BTreeMap, HashMap};
+use git::diff::DiffHunkStatus;
use gpui::{
- div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
- BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
- ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
- IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
- StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
- ViewContext, WeakView, WindowContext, WrappedLine,
+ div, overlay, point, px, relative, size, transparent_black, Action, AnchorCorner, AnyElement,
+ AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle,
+ DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, EntityId, Hsla,
+ InteractiveBounds, InteractiveElement, IntoElement, LineLayout, ModifiersChangedEvent,
+ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
+ ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
+ Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -139,8 +142,6 @@ impl EditorElement {
register_action(view, cx, Editor::move_right);
register_action(view, cx, Editor::move_down);
register_action(view, cx, Editor::move_up);
- // on_action(cx, Editor::new_file); todo!()
- // on_action(cx, Editor::new_file_in_direction); todo!()
register_action(view, cx, Editor::cancel);
register_action(view, cx, Editor::newline);
register_action(view, cx, Editor::newline_above);
@@ -263,7 +264,7 @@ impl EditorElement {
register_action(view, cx, Editor::fold_selected_ranges);
register_action(view, cx, Editor::show_completions);
register_action(view, cx, Editor::toggle_code_actions);
- // on_action(cx, Editor::open_excerpts); todo!()
+ register_action(view, cx, Editor::open_excerpts);
register_action(view, cx, Editor::toggle_soft_wrap);
register_action(view, cx, Editor::toggle_inlay_hints);
register_action(view, cx, hover_popover::hover);
@@ -312,7 +313,57 @@ impl EditorElement {
register_action(view, cx, Editor::context_menu_last);
}
- fn mouse_down(
+ fn register_key_listeners(&self, cx: &mut WindowContext) {
+ cx.on_key_event({
+ let editor = self.editor.clone();
+ move |event: &ModifiersChangedEvent, phase, cx| {
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if editor.update(cx, |editor, cx| Self::modifiers_changed(editor, event, cx)) {
+ cx.stop_propagation();
+ }
+ }
+ });
+ }
+
+ fn modifiers_changed(
+ editor: &mut Editor,
+ event: &ModifiersChangedEvent,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ let pending_selection = editor.has_pending_selection();
+
+ if let Some(point) = &editor.link_go_to_definition_state.last_trigger_point {
+ if event.command && !pending_selection {
+ let point = point.clone();
+ let snapshot = editor.snapshot(cx);
+ let kind = point.definition_kind(event.shift);
+
+ show_link_definition(kind, editor, point, snapshot, cx);
+ return false;
+ }
+ }
+
+ {
+ if editor.link_go_to_definition_state.symbol_range.is_some()
+ || !editor.link_go_to_definition_state.definitions.is_empty()
+ {
+ editor.link_go_to_definition_state.symbol_range.take();
+ editor.link_go_to_definition_state.definitions.clear();
+ cx.notify();
+ }
+
+ editor.link_go_to_definition_state.task = None;
+
+ editor.clear_highlights::<LinkGoToDefinitionState>(cx);
+ }
+
+ false
+ }
+
+ fn mouse_left_down(
editor: &mut Editor,
event: &MouseDownEvent,
position_map: &PositionMap,
@@ -365,25 +416,25 @@ impl EditorElement {
true
}
- // fn mouse_right_down(
- // editor: &mut Editor,
- // position: gpui::Point<Pixels>,
- // position_map: &PositionMap,
- // text_bounds: Bounds<Pixels>,
- // cx: &mut EventContext<Editor>,
- // ) -> bool {
- // if !text_bounds.contains_point(position) {
- // return false;
- // }
- // let point_for_position = position_map.point_for_position(text_bounds, position);
- // mouse_context_menu::deploy_context_menu(
- // editor,
- // position,
- // point_for_position.previous_valid,
- // cx,
- // );
- // true
- // }
+ fn mouse_right_down(
+ editor: &mut Editor,
+ event: &MouseDownEvent,
+ position_map: &PositionMap,
+ text_bounds: Bounds<Pixels>,
+ cx: &mut ViewContext<Editor>,
+ ) -> bool {
+ if !text_bounds.contains_point(&event.position) {
+ return false;
+ }
+ let point_for_position = position_map.point_for_position(text_bounds, event.position);
+ mouse_context_menu::deploy_context_menu(
+ editor,
+ event.position,
+ point_for_position.previous_valid,
+ cx,
+ );
+ true
+ }
fn mouse_up(
editor: &mut Editor,
@@ -725,87 +776,85 @@ impl EditorElement {
}
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
- // todo!()
- // let diff_style = &theme::current(cx).editor.diff.clone();
- // let line_height = layout.position_map.line_height;
-
- // let scroll_position = layout.position_map.snapshot.scroll_position();
- // let scroll_top = scroll_position.y * line_height;
-
- // for hunk in &layout.display_hunks {
- // let (display_row_range, status) = match hunk {
- // //TODO: This rendering is entirely a horrible hack
- // &DisplayDiffHunk::Folded { display_row: row } => {
- // let start_y = row as f32 * line_height - scroll_top;
- // let end_y = start_y + line_height;
-
- // let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(diff_style.modified),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (1. * line_height).into(),
- // });
-
- // continue;
- // }
-
- // DisplayDiffHunk::Unfolded {
- // display_row_range,
- // status,
- // } => (display_row_range, status),
- // };
-
- // let color = match status {
- // DiffHunkStatus::Added => diff_style.inserted,
- // DiffHunkStatus::Modified => diff_style.modified,
-
- // //TODO: This rendering is entirely a horrible hack
- // DiffHunkStatus::Removed => {
- // let row = display_row_range.start;
-
- // let offset = line_height / 2.;
- // let start_y = row as f32 * line_height - offset - scroll_top;
- // let end_y = start_y + line_height;
-
- // let width = diff_style.removed_width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(diff_style.deleted),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (1. * line_height).into(),
- // });
-
- // continue;
- // }
- // };
-
- // let start_row = display_row_range.start;
- // let end_row = display_row_range.end;
-
- // let start_y = start_row as f32 * line_height - scroll_top;
- // let end_y = end_row as f32 * line_height - scroll_top;
-
- // let width = diff_style.width_em * line_height;
- // let highlight_origin = bounds.origin + point(-width, start_y);
- // let highlight_size = point(width * 2., end_y - start_y);
- // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
- // cx.paint_quad(Quad {
- // bounds: highlight_bounds,
- // background: Some(color),
- // border: Border::new(0., Color::transparent_black()).into(),
- // corner_radii: (diff_style.corner_radius * line_height).into(),
- // });
- // }
+ let line_height = layout.position_map.line_height;
+
+ let scroll_position = layout.position_map.snapshot.scroll_position();
+ let scroll_top = scroll_position.y * line_height;
+
+ for hunk in &layout.display_hunks {
+ let (display_row_range, status) = match hunk {
+ //TODO: This rendering is entirely a horrible hack
+ &DisplayDiffHunk::Folded { display_row: row } => {
+ let start_y = row as f32 * line_height - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ gpui::yellow(), // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+
+ continue;
+ }
+
+ DisplayDiffHunk::Unfolded {
+ display_row_range,
+ status,
+ } => (display_row_range, status),
+ };
+
+ let color = match status {
+ DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color")
+ DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
+
+ //TODO: This rendering is entirely a horrible hack
+ DiffHunkStatus::Removed => {
+ let row = display_row_range.start;
+
+ let offset = line_height / 2.;
+ let start_y = row as f32 * line_height - offset - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(1. * line_height),
+ gpui::red(), // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+
+ continue;
+ }
+ };
+
+ let start_row = display_row_range.start;
+ let end_row = display_row_range.end;
+
+ let start_y = start_row as f32 * line_height - scroll_top;
+ let end_y = end_row as f32 * line_height - scroll_top;
+
+ let width = 0.275 * line_height;
+ let highlight_origin = bounds.origin + point(-width, start_y);
+ let highlight_size = size(width * 2., end_y - start_y);
+ let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
+ cx.paint_quad(
+ highlight_bounds,
+ Corners::all(0.05 * line_height),
+ color, // todo!("use the right color")
+ Edges::default(),
+ transparent_black(),
+ );
+ }
}
fn paint_text(
@@ -831,15 +880,19 @@ impl EditorElement {
bounds: text_bounds,
}),
|cx| {
- // todo!("cursor region")
- // cx.scene().push_cursor_region(CursorRegion {
- // bounds,
- // style: if !editor.link_go_to_definition_state.definitions.is_empty {
- // CursorStyle::PointingHand
- // } else {
- // CursorStyle::IBeam
- // },
- // });
+ if text_bounds.contains_point(&cx.mouse_position()) {
+ if self
+ .editor
+ .read(cx)
+ .link_go_to_definition_state
+ .definitions
+ .is_empty()
+ {
+ cx.set_cursor_style(CursorStyle::IBeam);
+ } else {
+ cx.set_cursor_style(CursorStyle::PointingHand);
+ }
+ }
let fold_corner_radius = 0.15 * layout.position_map.line_height;
cx.with_element_id(Some("folds"), |cx| {
@@ -1138,6 +1191,22 @@ impl EditorElement {
}
}
}
+
+ if let Some(mouse_context_menu) =
+ self.editor.read(cx).mouse_context_menu.as_ref()
+ {
+ let element = overlay()
+ .position(mouse_context_menu.position)
+ .child(mouse_context_menu.context_menu.clone())
+ .anchor(AnchorCorner::TopLeft)
+ .snap_to_window();
+ element.draw(
+ gpui::Point::default(),
+ size(AvailableSpace::MinContent, AvailableSpace::MinContent),
+ cx,
+ |_, _| {},
+ );
+ }
})
},
)
@@ -1662,11 +1731,6 @@ impl EditorElement {
cx: &mut WindowContext,
) -> LayoutState {
self.editor.update(cx, |editor, cx| {
- // let mut size = constraint.max;
- // if size.x.is_infinite() {
- // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
- // }
-
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
@@ -1702,6 +1766,7 @@ impl EditorElement {
};
editor.gutter_width = gutter_width;
+
let text_width = bounds.size.width - gutter_width;
let overscroll = size(em_width, px(0.));
let snapshot = {
@@ -1728,25 +1793,6 @@ impl EditorElement {
.collect::<SmallVec<[_; 2]>>();
let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
- // todo!("this should happen during layout")
- let editor_mode = snapshot.mode;
- if let EditorMode::AutoHeight { max_lines } = editor_mode {
- todo!()
- // size.set_y(
- // scroll_height
- // .min(constraint.max_along(Axis::Vertical))
- // .max(constraint.min_along(Axis::Vertical))
- // .max(line_height)
- // .min(line_height * max_lines as f32),
- // )
- } else if let EditorMode::SingleLine = editor_mode {
- bounds.size.height = line_height.min(bounds.size.height);
- }
- // todo!()
- // else if size.y.is_infinite() {
- // // size.set_y(scroll_height);
- // }
- //
let gutter_size = size(gutter_width, bounds.size.height);
let text_size = size(text_width, bounds.size.height);
@@ -2064,7 +2110,7 @@ impl EditorElement {
.unwrap();
LayoutState {
- mode: editor_mode,
+ mode: snapshot.mode,
position_map: Arc::new(PositionMap {
size: bounds.size,
scroll_position: point(
@@ -2308,10 +2354,10 @@ impl EditorElement {
return;
}
- let should_cancel = editor.update(cx, |editor, cx| {
+ let handled = editor.update(cx, |editor, cx| {
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
});
- if should_cancel {
+ if handled {
cx.stop_propagation();
}
}
@@ -2327,19 +2373,25 @@ impl EditorElement {
return;
}
- let should_cancel = editor.update(cx, |editor, cx| {
- Self::mouse_down(
- editor,
- event,
- &position_map,
- text_bounds,
- gutter_bounds,
- &stacking_order,
- cx,
- )
- });
+ let handled = match event.button {
+ MouseButton::Left => editor.update(cx, |editor, cx| {
+ Self::mouse_left_down(
+ editor,
+ event,
+ &position_map,
+ text_bounds,
+ gutter_bounds,
+ &stacking_order,
+ cx,
+ )
+ }),
+ MouseButton::Right => editor.update(cx, |editor, cx| {
+ Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
+ }),
+ _ => false,
+ };
- if should_cancel {
+ if handled {
cx.stop_propagation()
}
}
@@ -2351,7 +2403,7 @@ impl EditorElement {
let stacking_order = cx.stacking_order().clone();
move |event: &MouseUpEvent, phase, cx| {
- let should_cancel = editor.update(cx, |editor, cx| {
+ let handled = editor.update(cx, |editor, cx| {
Self::mouse_up(
editor,
event,
@@ -2362,26 +2414,11 @@ impl EditorElement {
)
});
- if should_cancel {
+ if handled {
cx.stop_propagation()
}
}
});
- //todo!()
- // on_down(MouseButton::Right, {
- // let position_map = layout.position_map.clone();
- // move |event, editor, cx| {
- // if !Self::mouse_right_down(
- // editor,
- // event.position,
- // position_map.as_ref(),
- // text_bounds,
- // cx,
- // ) {
- // cx.propagate_event();
- // }
- // }
- // });
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
@@ -2617,19 +2654,44 @@ impl Element for EditorElement {
cx: &mut gpui::WindowContext,
) -> (gpui::LayoutId, Self::State) {
self.editor.update(cx, |editor, cx| {
- editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
+ editor.set_style(self.style.clone(), cx);
- let rem_size = cx.rem_size();
- let mut style = Style::default();
- style.size.width = relative(1.).into();
- style.size.height = match editor.mode {
+ let layout_id = match editor.mode {
EditorMode::SingleLine => {
- self.style.text.line_height_in_pixels(cx.rem_size()).into()
+ let rem_size = cx.rem_size();
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
+ cx.request_layout(&style, None)
+ }
+ EditorMode::AutoHeight { max_lines } => {
+ let editor_handle = cx.view().clone();
+ let max_line_number_width =
+ self.max_line_number_width(&editor.snapshot(cx), cx);
+ cx.request_measured_layout(
+ Style::default(),
+ move |known_dimensions, available_space, cx| {
+ editor_handle
+ .update(cx, |editor, cx| {
+ compute_auto_height_layout(
+ editor,
+ max_lines,
+ max_line_number_width,
+ known_dimensions,
+ cx,
+ )
+ })
+ .unwrap_or_default()
+ },
+ )
+ }
+ EditorMode::Full => {
+ let mut style = Style::default();
+ style.size.width = relative(1.).into();
+ style.size.height = relative(1.).into();
+ cx.request_layout(&style, None)
}
- EditorMode::AutoHeight { .. } => todo!(),
- EditorMode::Full => relative(1.).into(),
};
- let layout_id = cx.request_layout(&style, None);
(layout_id, ())
})
@@ -2657,6 +2719,7 @@ impl Element for EditorElement {
let dispatch_context = self.editor.read(cx).dispatch_context(cx);
cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
self.register_actions(cx);
+ self.register_key_listeners(cx);
// We call with_z_index to establish a new stacking context.
cx.with_z_index(0, |cx| {
@@ -2698,604 +2761,6 @@ impl IntoElement for EditorElement {
}
}
-// impl EditorElement {
-// type LayoutState = LayoutState;
-// type PaintState = ();
-
-// fn layout(
-// &mut self,
-// constraint: SizeConstraint,
-// editor: &mut Editor,
-// cx: &mut ViewContext<Editor>,
-// ) -> (gpui::Point<Pixels>, Self::LayoutState) {
-// let mut size = constraint.max;
-// if size.x.is_infinite() {
-// unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-// }
-
-// let snapshot = editor.snapshot(cx);
-// let style = self.style.clone();
-
-// let line_height = (style.text.font_size * style.line_height_scalar).round();
-
-// let gutter_padding;
-// let gutter_width;
-// let gutter_margin;
-// if snapshot.show_gutter {
-// let em_width = style.text.em_width(cx.font_cache());
-// gutter_padding = (em_width * style.gutter_padding_factor).round();
-// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-// gutter_margin = -style.text.descent(cx.font_cache());
-// } else {
-// gutter_padding = 0.0;
-// gutter_width = 0.0;
-// gutter_margin = 0.0;
-// };
-
-// let text_width = size.x - gutter_width;
-// let em_width = style.text.em_width(cx.font_cache());
-// let em_advance = style.text.em_advance(cx.font_cache());
-// let overscroll = point(em_width, 0.);
-// let snapshot = {
-// editor.set_visible_line_count(size.y / line_height, cx);
-
-// let editor_width = text_width - gutter_margin - overscroll.x - em_width;
-// let wrap_width = match editor.soft_wrap_mode(cx) {
-// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
-// SoftWrap::EditorWidth => editor_width,
-// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
-// };
-
-// if editor.set_wrap_width(Some(wrap_width), cx) {
-// editor.snapshot(cx)
-// } else {
-// snapshot
-// }
-// };
-
-// let wrap_guides = editor
-// .wrap_guides(cx)
-// .iter()
-// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
-// .collect();
-
-// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
-// if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
-// size.set_y(
-// scroll_height
-// .min(constraint.max_along(Axis::Vertical))
-// .max(constraint.min_along(Axis::Vertical))
-// .max(line_height)
-// .min(line_height * max_lines as f32),
-// )
-// } else if let EditorMode::SingleLine = snapshot.mode {
-// size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
-// } else if size.y.is_infinite() {
-// size.set_y(scroll_height);
-// }
-// let gutter_size = point(gutter_width, size.y);
-// let text_size = point(text_width, size.y);
-
-// let autoscroll_horizontally = editor.autoscroll_vertically(size.y, line_height, cx);
-// let mut snapshot = editor.snapshot(cx);
-
-// let scroll_position = snapshot.scroll_position();
-// // The scroll position is a fractional point, the whole number of which represents
-// // the top of the window in terms of display rows.
-// let start_row = scroll_position.y as u32;
-// let height_in_lines = size.y / line_height;
-// let max_row = snapshot.max_point().row();
-
-// // Add 1 to ensure selections bleed off screen
-// let end_row = 1 + cmp::min(
-// (scroll_position.y + height_in_lines).ceil() as u32,
-// max_row,
-// );
-
-// let start_anchor = if start_row == 0 {
-// Anchor::min()
-// } else {
-// snapshot
-// .buffer_snapshot
-// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
-// };
-// let end_anchor = if end_row > max_row {
-// Anchor::max
-// } else {
-// snapshot
-// .buffer_snapshot
-// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
-// };
-
-// let mut selections: Vec<(SelectionStyle, Vec<SelectionLayout>)> = Vec::new();
-// let mut active_rows = BTreeMap::new();
-// let mut fold_ranges = Vec::new();
-// let is_singleton = editor.is_singleton(cx);
-
-// let highlighted_rows = editor.highlighted_rows();
-// let theme = theme::current(cx);
-// let highlighted_ranges = editor.background_highlights_in_range(
-// start_anchor..end_anchor,
-// &snapshot.display_snapshot,
-// theme.as_ref(),
-// );
-
-// fold_ranges.extend(
-// snapshot
-// .folds_in_range(start_anchor..end_anchor)
-// .map(|anchor| {
-// let start = anchor.start.to_point(&snapshot.buffer_snapshot);
-// (
-// start.row,
-// start.to_display_point(&snapshot.display_snapshot)
-// ..anchor.end.to_display_point(&snapshot),
-// )
-// }),
-// );
-
-// let mut newest_selection_head = None;
-
-// if editor.show_local_selections {
-// let mut local_selections: Vec<Selection<Point>> = editor
-// .selections
-// .disjoint_in_range(start_anchor..end_anchor, cx);
-// local_selections.extend(editor.selections.pending(cx));
-// let mut layouts = Vec::new();
-// let newest = editor.selections.newest(cx);
-// for selection in local_selections.drain(..) {
-// let is_empty = selection.start == selection.end;
-// let is_newest = selection == newest;
-
-// let layout = SelectionLayout::new(
-// selection,
-// editor.selections.line_mode,
-// editor.cursor_shape,
-// &snapshot.display_snapshot,
-// is_newest,
-// true,
-// );
-// if is_newest {
-// newest_selection_head = Some(layout.head);
-// }
-
-// for row in cmp::max(layout.active_rows.start, start_row)
-// ..=cmp::min(layout.active_rows.end, end_row)
-// {
-// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
-// *contains_non_empty_selection |= !is_empty;
-// }
-// layouts.push(layout);
-// }
-
-// selections.push((style.selection, layouts));
-// }
-
-// if let Some(collaboration_hub) = &editor.collaboration_hub {
-// // When following someone, render the local selections in their color.
-// if let Some(leader_id) = editor.leader_peer_id {
-// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-// if let Some(participant_index) = collaboration_hub
-// .user_participant_indices(cx)
-// .get(&collaborator.user_id)
-// {
-// if let Some((local_selection_style, _)) = selections.first_mut() {
-// *local_selection_style =
-// style.selection_style_for_room_participant(participant_index.0);
-// }
-// }
-// }
-// }
-
-// let mut remote_selections = HashMap::default();
-// for selection in snapshot.remote_selections_in_range(
-// &(start_anchor..end_anchor),
-// collaboration_hub.as_ref(),
-// cx,
-// ) {
-// let selection_style = if let Some(participant_index) = selection.participant_index {
-// style.selection_style_for_room_participant(participant_index.0)
-// } else {
-// style.absent_selection
-// };
-
-// // Don't re-render the leader's selections, since the local selections
-// // match theirs.
-// if Some(selection.peer_id) == editor.leader_peer_id {
-// continue;
-// }
-
-// remote_selections
-// .entry(selection.replica_id)
-// .or_insert((selection_style, Vec::new()))
-// .1
-// .push(SelectionLayout::new(
-// selection.selection,
-// selection.line_mode,
-// selection.cursor_shape,
-// &snapshot.display_snapshot,
-// false,
-// false,
-// ));
-// }
-
-// selections.extend(remote_selections.into_values());
-// }
-
-// let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
-// let show_scrollbars = match scrollbar_settings.show {
-// ShowScrollbar::Auto => {
-// // Git
-// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
-// ||
-// // Selections
-// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty)
-// // Scrollmanager
-// || editor.scroll_manager.scrollbars_visible()
-// }
-// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
-// ShowScrollbar::Always => true,
-// ShowScrollbar::Never => false,
-// };
-
-// let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
-// .into_iter()
-// .map(|(id, fold)| {
-// let color = self
-// .style
-// .folds
-// .ellipses
-// .background
-// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-// .color;
-
-// (id, fold, color)
-// })
-// .collect();
-
-// let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-// let newest = editor.selections.newest::<Point>(cx);
-// SelectionLayout::new(
-// newest,
-// editor.selections.line_mode,
-// editor.cursor_shape,
-// &snapshot.display_snapshot,
-// true,
-// true,
-// )
-// .head
-// });
-
-// let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
-// start_row..end_row,
-// &active_rows,
-// head_for_relative,
-// is_singleton,
-// &snapshot,
-// cx,
-// );
-
-// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
-// let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
-
-// let mut max_visible_line_width = 0.0;
-// let line_layouts =
-// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
-// for line_with_invisibles in &line_layouts {
-// if line_with_invisibles.line.width() > max_visible_line_width {
-// max_visible_line_width = line_with_invisibles.line.width();
-// }
-// }
-
-// let style = self.style.clone();
-// let longest_line_width = layout_line(
-// snapshot.longest_row(),
-// &snapshot,
-// &style,
-// cx.text_layout_cache(),
-// )
-// .width();
-// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x;
-// let em_width = style.text.em_width(cx.font_cache());
-// let (scroll_width, blocks) = self.layout_blocks(
-// start_row..end_row,
-// &snapshot,
-// size.x,
-// scroll_width,
-// gutter_padding,
-// gutter_width,
-// em_width,
-// gutter_width + gutter_margin,
-// line_height,
-// &style,
-// &line_layouts,
-// editor,
-// cx,
-// );
-
-// let scroll_max = point(
-// ((scroll_width - text_size.x) / em_width).max(0.0),
-// max_row as f32,
-// );
-
-// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
-// let autoscrolled = if autoscroll_horizontally {
-// editor.autoscroll_horizontally(
-// start_row,
-// text_size.x,
-// scroll_width,
-// em_width,
-// &line_layouts,
-// cx,
-// )
-// } else {
-// false
-// };
-
-// if clamped || autoscrolled {
-// snapshot = editor.snapshot(cx);
-// }
-
-// let style = editor.style(cx);
-
-// let mut context_menu = None;
-// let mut code_actions_indicator = None;
-// if let Some(newest_selection_head) = newest_selection_head {
-// if (start_row..end_row).contains(&newest_selection_head.row()) {
-// if editor.context_menu_visible() {
-// context_menu =
-// editor.render_context_menu(newest_selection_head, style.clone(), cx);
-// }
-
-// let active = matches!(
-// editor.context_menu.read().as_ref(),
-// Some(crate::ContextMenu::CodeActions(_))
-// );
-
-// code_actions_indicator = editor
-// .render_code_actions_indicator(&style, active, cx)
-// .map(|indicator| (newest_selection_head.row(), indicator));
-// }
-// }
-
-// let visible_rows = start_row..start_row + line_layouts.len() as u32;
-// let mut hover = editor.hover_state.render(
-// &snapshot,
-// &style,
-// visible_rows,
-// editor.workspace.as_ref().map(|(w, _)| w.clone()),
-// cx,
-// );
-// let mode = editor.mode;
-
-// let mut fold_indicators = editor.render_fold_indicators(
-// fold_statuses,
-// &style,
-// editor.gutter_hovered,
-// line_height,
-// gutter_margin,
-// cx,
-// );
-
-// if let Some((_, context_menu)) = context_menu.as_mut() {
-// context_menu.layout(
-// SizeConstraint {
-// min: gpui::Point::<Pixels>::zero(),
-// max: point(
-// cx.window_size().x * 0.7,
-// (12. * line_height).min((size.y - line_height) / 2.),
-// ),
-// },
-// editor,
-// cx,
-// );
-// }
-
-// if let Some((_, indicator)) = code_actions_indicator.as_mut() {
-// indicator.layout(
-// SizeConstraint::strict_along(
-// Axis::Vertical,
-// line_height * style.code_actions.vertical_scale,
-// ),
-// editor,
-// cx,
-// );
-// }
-
-// for fold_indicator in fold_indicators.iter_mut() {
-// if let Some(indicator) = fold_indicator.as_mut() {
-// indicator.layout(
-// SizeConstraint::strict_along(
-// Axis::Vertical,
-// line_height * style.code_actions.vertical_scale,
-// ),
-// editor,
-// cx,
-// );
-// }
-// }
-
-// if let Some((_, hover_popovers)) = hover.as_mut() {
-// for hover_popover in hover_popovers.iter_mut() {
-// hover_popover.layout(
-// SizeConstraint {
-// min: gpui::Point::<Pixels>::zero(),
-// max: point(
-// (120. * em_width) // Default size
-// .min(size.x / 2.) // Shrink to half of the editor width
-// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
-// (16. * line_height) // Default size
-// .min(size.y / 2.) // Shrink to half of the editor height
-// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-// ),
-// },
-// editor,
-// cx,
-// );
-// }
-// }
-
-// let invisible_symbol_font_size = self.style.text.font_size / 2.0;
-// let invisible_symbol_style = RunStyle {
-// color: self.style.whitespace,
-// font_id: self.style.text.font_id,
-// underline: Default::default(),
-// };
-
-// (
-// size,
-// LayoutState {
-// mode,
-// position_map: Arc::new(PositionMap {
-// size,
-// scroll_max,
-// line_layouts,
-// line_height,
-// em_width,
-// em_advance,
-// snapshot,
-// }),
-// visible_display_row_range: start_row..end_row,
-// wrap_guides,
-// gutter_size,
-// gutter_padding,
-// text_size,
-// scrollbar_row_range,
-// show_scrollbars,
-// is_singleton,
-// max_row,
-// gutter_margin,
-// active_rows,
-// highlighted_rows,
-// highlighted_ranges,
-// fold_ranges,
-// line_number_layouts,
-// display_hunks,
-// blocks,
-// selections,
-// context_menu,
-// code_actions_indicator,
-// fold_indicators,
-// tab_invisible: cx.text_layout_cache().layout_str(
-// "β",
-// invisible_symbol_font_size,
-// &[("β".len(), invisible_symbol_style)],
-// ),
-// space_invisible: cx.text_layout_cache().layout_str(
-// "β’",
-// invisible_symbol_font_size,
-// &[("β’".len(), invisible_symbol_style)],
-// ),
-// hover_popovers: hover,
-// },
-// )
-// }
-
-// fn paint(
-// &mut self,
-// bounds: Bounds<Pixels>,
-// visible_bounds: Bounds<Pixels>,
-// layout: &mut Self::LayoutState,
-// editor: &mut Editor,
-// cx: &mut ViewContext<Editor>,
-// ) -> Self::PaintState {
-// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-// cx.scene().push_layer(Some(visible_bounds));
-
-// let gutter_bounds = Bounds::<Pixels>::new(bounds.origin, layout.gutter_size);
-// let text_bounds = Bounds::<Pixels>::new(
-// bounds.origin + point(layout.gutter_size.x, 0.0),
-// layout.text_size,
-// );
-
-// Self::attach_mouse_handlers(
-// &layout.position_map,
-// layout.hover_popovers.is_some(),
-// visible_bounds,
-// text_bounds,
-// gutter_bounds,
-// bounds,
-// cx,
-// );
-
-// self.paint_background(gutter_bounds, text_bounds, layout, cx);
-// if layout.gutter_size.x > 0. {
-// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
-// }
-// self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
-
-// cx.scene().push_layer(Some(bounds));
-// if !layout.blocks.is_empty {
-// self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
-// }
-// self.paint_scrollbar(bounds, layout, &editor, cx);
-// cx.scene().pop_layer();
-// cx.scene().pop_layer();
-// }
-
-// fn rect_for_text_range(
-// &self,
-// range_utf16: Range<usize>,
-// bounds: Bounds<Pixels>,
-// _: Bounds<Pixels>,
-// layout: &Self::LayoutState,
-// _: &Self::PaintState,
-// _: &Editor,
-// _: &ViewContext<Editor>,
-// ) -> Option<Bounds<Pixels>> {
-// let text_bounds = Bounds::<Pixels>::new(
-// bounds.origin + point(layout.gutter_size.x, 0.0),
-// layout.text_size,
-// );
-// let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.);
-// let scroll_position = layout.position_map.snapshot.scroll_position();
-// let start_row = scroll_position.y as u32;
-// let scroll_top = scroll_position.y * layout.position_map.line_height;
-// let scroll_left = scroll_position.x * layout.position_map.em_width;
-
-// let range_start = OffsetUtf16(range_utf16.start)
-// .to_display_point(&layout.position_map.snapshot.display_snapshot);
-// if range_start.row() < start_row {
-// return None;
-// }
-
-// let line = &layout
-// .position_map
-// .line_layouts
-// .get((range_start.row() - start_row) as usize)?
-// .line;
-// let range_start_x = line.x_for_index(range_start.column() as usize);
-// let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
-// Some(Bounds::<Pixels>::new(
-// content_origin
-// + point(
-// range_start_x,
-// range_start_y + layout.position_map.line_height,
-// )
-// - point(scroll_left, scroll_top),
-// point(
-// layout.position_map.em_width,
-// layout.position_map.line_height,
-// ),
-// ))
-// }
-
-// fn debug(
-// &self,
-// bounds: Bounds<Pixels>,
-// _: &Self::LayoutState,
-// _: &Self::PaintState,
-// _: &Editor,
-// _: &ViewContext<Editor>,
-// ) -> json::Value {
-// json!({
-// "type": "BufferElement",
-// "bounds": bounds.to_json()
-// })
-// }
-// }
-
type BufferRow = u32;
pub struct LayoutState {
@@ -4,13 +4,14 @@ use crate::{
EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
- div, point, AnyElement, AppContext, AsyncAppContext, 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},
@@ -71,110 +76,108 @@ impl FollowableItem for Editor {
workspace: View<Workspace>,
remote_id: ViewId,
state: &mut Option<proto::view::Variant>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Task<Result<View<Self>>>> {
- todo!()
+ let project = workspace.read(cx).project().to_owned();
+ let Some(proto::view::Variant::Editor(_)) = state else {
+ return None;
+ };
+ let Some(proto::view::Variant::Editor(state)) = state.take() else {
+ unreachable!()
+ };
+
+ let client = project.read(cx).client();
+ let replica_id = project.read(cx).replica_id();
+ let buffer_ids = state
+ .excerpts
+ .iter()
+ .map(|excerpt| excerpt.buffer_id)
+ .collect::<HashSet<_>>();
+ let buffers = project.update(cx, |project, cx| {
+ buffer_ids
+ .iter()
+ .map(|id| project.open_buffer_by_id(*id, cx))
+ .collect::<Vec<_>>()
+ });
+
+ let pane = pane.downgrade();
+ Some(cx.spawn(|mut cx| async move {
+ let mut buffers = futures::future::try_join_all(buffers).await?;
+ let editor = pane.update(&mut cx, |pane, cx| {
+ let mut editors = pane.items_of_type::<Self>();
+ editors.find(|editor| {
+ let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
+ let singleton_buffer_matches = state.singleton
+ && buffers.first()
+ == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
+ ids_match || singleton_buffer_matches
+ })
+ })?;
+
+ let editor = if let Some(editor) = editor {
+ editor
+ } else {
+ pane.update(&mut cx, |_, cx| {
+ let multibuffer = cx.build_model(|cx| {
+ let mut multibuffer;
+ if state.singleton && buffers.len() == 1 {
+ multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
+ } else {
+ multibuffer = MultiBuffer::new(replica_id);
+ let mut excerpts = state.excerpts.into_iter().peekable();
+ while let Some(excerpt) = excerpts.peek() {
+ let buffer_id = excerpt.buffer_id;
+ let buffer_excerpts = iter::from_fn(|| {
+ let excerpt = excerpts.peek()?;
+ (excerpt.buffer_id == buffer_id)
+ .then(|| excerpts.next().unwrap())
+ });
+ let buffer =
+ buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
+ if let Some(buffer) = buffer {
+ multibuffer.push_excerpts(
+ buffer.clone(),
+ buffer_excerpts.filter_map(deserialize_excerpt_range),
+ cx,
+ );
+ }
+ }
+ };
+
+ if let Some(title) = &state.title {
+ multibuffer = multibuffer.with_title(title.clone())
+ }
+
+ multibuffer
+ });
+
+ cx.build_view(|cx| {
+ let mut editor =
+ Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
+ editor.remote_id = Some(remote_id);
+ editor
+ })
+ })?
+ };
+
+ update_editor_from_message(
+ editor.downgrade(),
+ project,
+ proto::update_view::Editor {
+ selections: state.selections,
+ pending_selection: state.pending_selection,
+ scroll_top_anchor: state.scroll_top_anchor,
+ scroll_x: state.scroll_x,
+ scroll_y: state.scroll_y,
+ ..Default::default()
+ },
+ &mut cx,
+ )
+ .await?;
+
+ Ok(editor)
+ }))
}
- // let project = workspace.read(cx).project().to_owned();
- // let Some(proto::view::Variant::Editor(_)) = state else {
- // return None;
- // };
- // let Some(proto::view::Variant::Editor(state)) = state.take() else {
- // unreachable!()
- // };
-
- // let client = project.read(cx).client();
- // let replica_id = project.read(cx).replica_id();
- // let buffer_ids = state
- // .excerpts
- // .iter()
- // .map(|excerpt| excerpt.buffer_id)
- // .collect::<HashSet<_>>();
- // let buffers = project.update(cx, |project, cx| {
- // buffer_ids
- // .iter()
- // .map(|id| project.open_buffer_by_id(*id, cx))
- // .collect::<Vec<_>>()
- // });
-
- // let pane = pane.downgrade();
- // Some(cx.spawn(|mut cx| async move {
- // let mut buffers = futures::future::try_join_all(buffers).await?;
- // let editor = pane.read_with(&cx, |pane, cx| {
- // let mut editors = pane.items_of_type::<Self>();
- // editors.find(|editor| {
- // let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
- // let singleton_buffer_matches = state.singleton
- // && buffers.first()
- // == editor.read(cx).buffer.read(cx).as_singleton().as_ref();
- // ids_match || singleton_buffer_matches
- // })
- // })?;
-
- // let editor = if let Some(editor) = editor {
- // editor
- // } else {
- // pane.update(&mut cx, |_, cx| {
- // let multibuffer = cx.add_model(|cx| {
- // let mut multibuffer;
- // if state.singleton && buffers.len() == 1 {
- // multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
- // } else {
- // multibuffer = MultiBuffer::new(replica_id);
- // let mut excerpts = state.excerpts.into_iter().peekable();
- // while let Some(excerpt) = excerpts.peek() {
- // let buffer_id = excerpt.buffer_id;
- // let buffer_excerpts = iter::from_fn(|| {
- // let excerpt = excerpts.peek()?;
- // (excerpt.buffer_id == buffer_id)
- // .then(|| excerpts.next().unwrap())
- // });
- // let buffer =
- // buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
- // if let Some(buffer) = buffer {
- // multibuffer.push_excerpts(
- // buffer.clone(),
- // buffer_excerpts.filter_map(deserialize_excerpt_range),
- // cx,
- // );
- // }
- // }
- // };
-
- // if let Some(title) = &state.title {
- // multibuffer = multibuffer.with_title(title.clone())
- // }
-
- // multibuffer
- // });
-
- // cx.add_view(|cx| {
- // let mut editor =
- // Editor::for_multibuffer(multibuffer, Some(project.clone()), cx);
- // editor.remote_id = Some(remote_id);
- // editor
- // })
- // })?
- // };
-
- // update_editor_from_message(
- // editor.downgrade(),
- // project,
- // proto::update_view::Editor {
- // selections: state.selections,
- // pending_selection: state.pending_selection,
- // scroll_top_anchor: state.scroll_top_anchor,
- // scroll_x: state.scroll_x,
- // scroll_y: state.scroll_y,
- // ..Default::default()
- // },
- // &mut cx,
- // )
- // .await?;
-
- // Ok(editor)
- // }))
- // }
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
self.leader_peer_id = leader_peer_id;
@@ -195,7 +198,7 @@ impl FollowableItem for Editor {
cx.notify();
}
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
let buffer = self.buffer.read(cx);
let scroll_anchor = self.scroll_manager.anchor();
let excerpts = buffer
@@ -242,7 +245,7 @@ impl FollowableItem for Editor {
&self,
event: &Self::FollowableEvent,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool {
let update =
update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
@@ -315,7 +318,7 @@ impl FollowableItem for Editor {
})
}
- fn is_project_item(&self, _cx: &AppContext) -> bool {
+ fn is_project_item(&self, _cx: &WindowContext) -> bool {
true
}
}
@@ -324,132 +327,129 @@ async fn update_editor_from_message(
this: WeakView<Editor>,
project: Model<Project>,
message: proto::update_view::Editor,
- cx: &mut AsyncAppContext,
+ cx: &mut AsyncWindowContext,
) -> Result<()> {
- todo!()
+ // Open all of the buffers of which excerpts were added to the editor.
+ let inserted_excerpt_buffer_ids = message
+ .inserted_excerpts
+ .iter()
+ .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
+ .collect::<HashSet<_>>();
+ let inserted_excerpt_buffers = project.update(cx, |project, cx| {
+ inserted_excerpt_buffer_ids
+ .into_iter()
+ .map(|id| project.open_buffer_by_id(id, cx))
+ .collect::<Vec<_>>()
+ })?;
+ let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
+
+ // Update the editor's excerpts.
+ this.update(cx, |editor, cx| {
+ editor.buffer.update(cx, |multibuffer, cx| {
+ let mut removed_excerpt_ids = message
+ .deleted_excerpts
+ .into_iter()
+ .map(ExcerptId::from_proto)
+ .collect::<Vec<_>>();
+ removed_excerpt_ids.sort_by({
+ let multibuffer = multibuffer.read(cx);
+ move |a, b| a.cmp(&b, &multibuffer)
+ });
+
+ let mut insertions = message.inserted_excerpts.into_iter().peekable();
+ while let Some(insertion) = insertions.next() {
+ let Some(excerpt) = insertion.excerpt else {
+ continue;
+ };
+ let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
+ continue;
+ };
+ let buffer_id = excerpt.buffer_id;
+ let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
+ continue;
+ };
+
+ let adjacent_excerpts = iter::from_fn(|| {
+ let insertion = insertions.peek()?;
+ if insertion.previous_excerpt_id.is_none()
+ && insertion.excerpt.as_ref()?.buffer_id == buffer_id
+ {
+ insertions.next()?.excerpt
+ } else {
+ None
+ }
+ });
+
+ multibuffer.insert_excerpts_with_ids_after(
+ ExcerptId::from_proto(previous_excerpt_id),
+ buffer,
+ [excerpt]
+ .into_iter()
+ .chain(adjacent_excerpts)
+ .filter_map(|excerpt| {
+ Some((
+ ExcerptId::from_proto(excerpt.id),
+ deserialize_excerpt_range(excerpt)?,
+ ))
+ }),
+ cx,
+ );
+ }
+
+ multibuffer.remove_excerpts(removed_excerpt_ids, cx);
+ });
+ })?;
+
+ // Deserialize the editor state.
+ let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
+ let buffer = editor.buffer.read(cx).read(cx);
+ let selections = message
+ .selections
+ .into_iter()
+ .filter_map(|selection| deserialize_selection(&buffer, selection))
+ .collect::<Vec<_>>();
+ let pending_selection = message
+ .pending_selection
+ .and_then(|selection| deserialize_selection(&buffer, selection));
+ let scroll_top_anchor = message
+ .scroll_top_anchor
+ .and_then(|anchor| deserialize_anchor(&buffer, anchor));
+ anyhow::Ok((selections, pending_selection, scroll_top_anchor))
+ })??;
+
+ // Wait until the buffer has received all of the operations referenced by
+ // the editor's new state.
+ this.update(cx, |editor, cx| {
+ editor.buffer.update(cx, |buffer, cx| {
+ buffer.wait_for_anchors(
+ selections
+ .iter()
+ .chain(pending_selection.as_ref())
+ .flat_map(|selection| [selection.start, selection.end])
+ .chain(scroll_top_anchor),
+ cx,
+ )
+ })
+ })?
+ .await?;
+
+ // Update the editor's state.
+ this.update(cx, |editor, cx| {
+ if !selections.is_empty() || pending_selection.is_some() {
+ editor.set_selections_from_remote(selections, pending_selection, cx);
+ editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
+ } else if let Some(scroll_top_anchor) = scroll_top_anchor {
+ editor.set_scroll_anchor_remote(
+ ScrollAnchor {
+ anchor: scroll_top_anchor,
+ offset: point(message.scroll_x, message.scroll_y),
+ },
+ cx,
+ );
+ }
+ })?;
+ Ok(())
}
-// Previous implementation of the above
-// // Open all of the buffers of which excerpts were added to the editor.
-// let inserted_excerpt_buffer_ids = message
-// .inserted_excerpts
-// .iter()
-// .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
-// .collect::<HashSet<_>>();
-// let inserted_excerpt_buffers = project.update(cx, |project, cx| {
-// inserted_excerpt_buffer_ids
-// .into_iter()
-// .map(|id| project.open_buffer_by_id(id, cx))
-// .collect::<Vec<_>>()
-// })?;
-// let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
-
-// // Update the editor's excerpts.
-// this.update(cx, |editor, cx| {
-// editor.buffer.update(cx, |multibuffer, cx| {
-// let mut removed_excerpt_ids = message
-// .deleted_excerpts
-// .into_iter()
-// .map(ExcerptId::from_proto)
-// .collect::<Vec<_>>();
-// removed_excerpt_ids.sort_by({
-// let multibuffer = multibuffer.read(cx);
-// move |a, b| a.cmp(&b, &multibuffer)
-// });
-
-// let mut insertions = message.inserted_excerpts.into_iter().peekable();
-// while let Some(insertion) = insertions.next() {
-// let Some(excerpt) = insertion.excerpt else {
-// continue;
-// };
-// let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
-// continue;
-// };
-// let buffer_id = excerpt.buffer_id;
-// let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
-// continue;
-// };
-
-// let adjacent_excerpts = iter::from_fn(|| {
-// let insertion = insertions.peek()?;
-// if insertion.previous_excerpt_id.is_none()
-// && insertion.excerpt.as_ref()?.buffer_id == buffer_id
-// {
-// insertions.next()?.excerpt
-// } else {
-// None
-// }
-// });
-
-// multibuffer.insert_excerpts_with_ids_after(
-// ExcerptId::from_proto(previous_excerpt_id),
-// buffer,
-// [excerpt]
-// .into_iter()
-// .chain(adjacent_excerpts)
-// .filter_map(|excerpt| {
-// Some((
-// ExcerptId::from_proto(excerpt.id),
-// deserialize_excerpt_range(excerpt)?,
-// ))
-// }),
-// cx,
-// );
-// }
-
-// multibuffer.remove_excerpts(removed_excerpt_ids, cx);
-// });
-// })?;
-
-// // Deserialize the editor state.
-// let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
-// let buffer = editor.buffer.read(cx).read(cx);
-// let selections = message
-// .selections
-// .into_iter()
-// .filter_map(|selection| deserialize_selection(&buffer, selection))
-// .collect::<Vec<_>>();
-// let pending_selection = message
-// .pending_selection
-// .and_then(|selection| deserialize_selection(&buffer, selection));
-// let scroll_top_anchor = message
-// .scroll_top_anchor
-// .and_then(|anchor| deserialize_anchor(&buffer, anchor));
-// anyhow::Ok((selections, pending_selection, scroll_top_anchor))
-// })??;
-
-// // Wait until the buffer has received all of the operations referenced by
-// // the editor's new state.
-// this.update(cx, |editor, cx| {
-// editor.buffer.update(cx, |buffer, cx| {
-// buffer.wait_for_anchors(
-// selections
-// .iter()
-// .chain(pending_selection.as_ref())
-// .flat_map(|selection| [selection.start, selection.end])
-// .chain(scroll_top_anchor),
-// cx,
-// )
-// })
-// })?
-// .await?;
-
-// // Update the editor's state.
-// this.update(cx, |editor, cx| {
-// if !selections.is_empty() || pending_selection.is_some() {
-// editor.set_selections_from_remote(selections, pending_selection, cx);
-// editor.request_autoscroll_remotely(Autoscroll::newest(), cx);
-// } else if let Some(scroll_top_anchor) = scroll_top_anchor {
-// editor.set_scroll_anchor_remote(
-// ScrollAnchor {
-// anchor: scroll_top_anchor,
-// offset: point(message.scroll_x, message.scroll_y),
-// },
-// cx,
-// );
-// }
-// })?;
-// Ok(())
-// }
fn serialize_excerpt(
buffer_id: u64,
@@ -529,39 +529,38 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
impl Item for Editor {
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
- todo!();
- // if let Ok(data) = data.downcast::<NavigationData>() {
- // let newest_selection = self.selections.newest::<Point>(cx);
- // let buffer = self.buffer.read(cx).read(cx);
- // let offset = if buffer.can_resolve(&data.cursor_anchor) {
- // data.cursor_anchor.to_point(&buffer)
- // } else {
- // buffer.clip_point(data.cursor_position, Bias::Left)
- // };
-
- // let mut scroll_anchor = data.scroll_anchor;
- // if !buffer.can_resolve(&scroll_anchor.anchor) {
- // scroll_anchor.anchor = buffer.anchor_before(
- // buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
- // );
- // }
-
- // drop(buffer);
-
- // if newest_selection.head() == offset {
- // false
- // } else {
- // let nav_history = self.nav_history.take();
- // self.set_scroll_anchor(scroll_anchor, cx);
- // self.change_selections(Some(Autoscroll::fit()), cx, |s| {
- // s.select_ranges([offset..offset])
- // });
- // self.nav_history = nav_history;
- // true
- // }
- // } else {
- // false
- // }
+ if let Ok(data) = data.downcast::<NavigationData>() {
+ let newest_selection = self.selections.newest::<Point>(cx);
+ let buffer = self.buffer.read(cx).read(cx);
+ let offset = if buffer.can_resolve(&data.cursor_anchor) {
+ data.cursor_anchor.to_point(&buffer)
+ } else {
+ buffer.clip_point(data.cursor_position, Bias::Left)
+ };
+
+ let mut scroll_anchor = data.scroll_anchor;
+ if !buffer.can_resolve(&scroll_anchor.anchor) {
+ scroll_anchor.anchor = buffer.anchor_before(
+ buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
+ );
+ }
+
+ drop(buffer);
+
+ if newest_selection.head() == offset {
+ false
+ } else {
+ let nav_history = self.nav_history.take();
+ self.set_scroll_anchor(scroll_anchor, cx);
+ self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges([offset..offset])
+ });
+ self.nav_history = nav_history;
+ true
+ }
+ } else {
+ false
+ }
}
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
@@ -768,7 +767,7 @@ impl Item for Editor {
let cursor = self.selections.newest_anchor().head();
let multibuffer = &self.buffer().read(cx);
let (buffer_id, symbols) =
- multibuffer.symbols_containing(cursor, Some(&cx.theme().styles.syntax), cx)?;
+ multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
let buffer = multibuffer.buffer(buffer_id)?;
let buffer = buffer.read(cx);
@@ -788,19 +787,9 @@ impl Item for Editor {
text: filename,
highlights: None,
}];
- breadcrumbs.extend(symbols.into_iter().map(|symbol| {
- // eprintln!(
- // "ranges: {:?}",
- // symbol
- // .highlight_ranges
- // .iter()
- // .map(|range| &symbol.text[range.0.clone()])
- // .collect::<Vec<_>>()
- // );
- BreadcrumbText {
- text: symbol.text,
- highlights: Some(symbol.highlight_ranges),
- }
+ breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+ text: symbol.text,
+ highlights: Some(symbol.highlight_ranges),
}));
Some(breadcrumbs)
}
@@ -1129,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>,
@@ -1,5 +1,14 @@
-use crate::{DisplayPoint, Editor, EditorMode, SelectMode};
-use gpui::{Pixels, Point, ViewContext};
+use crate::{
+ DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
+ Rename, RevealInFinder, SelectMode, ToggleCodeActions,
+};
+use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
+
+pub struct MouseContextMenu {
+ pub(crate) position: Point<Pixels>,
+ pub(crate) context_menu: View<ui::ContextMenu>,
+ _subscription: Subscription,
+}
pub fn deploy_context_menu(
editor: &mut Editor,
@@ -7,50 +16,57 @@ pub fn deploy_context_menu(
point: DisplayPoint,
cx: &mut ViewContext<Editor>,
) {
- todo!();
+ if !editor.is_focused(cx) {
+ editor.focus(cx);
+ }
+
+ // Don't show context menu for inline editors
+ if editor.mode() != EditorMode::Full {
+ return;
+ }
- // if !editor.focused {
- // cx.focus_self();
- // }
+ // Don't show the context menu if there isn't a project associated with this editor
+ if editor.project.is_none() {
+ return;
+ }
- // // Don't show context menu for inline editors
- // if editor.mode() != EditorMode::Full {
- // return;
- // }
+ // Move the cursor to the clicked location so that dispatched actions make sense
+ editor.change_selections(None, cx, |s| {
+ s.clear_disjoint();
+ s.set_pending_display_range(point..point, SelectMode::Character);
+ });
- // // Don't show the context menu if there isn't a project associated with this editor
- // if editor.project.is_none() {
- // return;
- // }
+ let context_menu = ui::ContextMenu::build(cx, |menu, cx| {
+ menu.action("Rename Symbol", Box::new(Rename), cx)
+ .action("Go to Definition", Box::new(GoToDefinition), cx)
+ .action("Go to Type Definition", Box::new(GoToTypeDefinition), cx)
+ .action("Find All References", Box::new(FindAllReferences), cx)
+ .action(
+ "Code Actions",
+ Box::new(ToggleCodeActions {
+ deployed_from_indicator: false,
+ }),
+ cx,
+ )
+ .separator()
+ .action("Reveal in Finder", Box::new(RevealInFinder), cx)
+ });
+ let context_menu_focus = context_menu.focus_handle(cx);
+ cx.focus(&context_menu_focus);
- // // Move the cursor to the clicked location so that dispatched actions make sense
- // editor.change_selections(None, cx, |s| {
- // s.clear_disjoint();
- // s.set_pending_display_range(point..point, SelectMode::Character);
- // });
+ let _subscription = cx.subscribe(&context_menu, move |this, _, event: &DismissEvent, cx| {
+ this.mouse_context_menu.take();
+ if context_menu_focus.contains_focused(cx) {
+ this.focus(cx);
+ }
+ });
- // editor.mouse_context_menu.update(cx, |menu, cx| {
- // menu.show(
- // position,
- // AnchorCorner::TopLeft,
- // vec![
- // ContextMenuItem::action("Rename Symbol", Rename),
- // ContextMenuItem::action("Go to Definition", GoToDefinition),
- // ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
- // ContextMenuItem::action("Find All References", FindAllReferences),
- // ContextMenuItem::action(
- // "Code Actions",
- // ToggleCodeActions {
- // deployed_from_indicator: false,
- // },
- // ),
- // ContextMenuItem::Separator,
- // ContextMenuItem::action("Reveal in Finder", RevealInFinder),
- // ],
- // cx,
- // );
- // });
- // cx.notify();
+ editor.mouse_context_menu = Some(MouseContextMenu {
+ position,
+ context_menu,
+ _subscription,
+ });
+ cx.notify();
}
// #[cfg(test)]
@@ -315,14 +315,11 @@ impl SelectionsCollection {
let line = display_map.layout_row(row, &text_layout_details);
- dbg!("****START COL****");
let start_col = line.closest_index_for_x(positions.start) as u32;
if start_col < line_len || (is_empty && positions.start == line.width) {
let start = DisplayPoint::new(row, start_col);
- dbg!("****END COL****");
let end_col = line.closest_index_for_x(positions.end) as u32;
let end = DisplayPoint::new(row, end_col);
- dbg!(start_col, end_col);
Some(Selection {
id: post_inc(&mut self.next_selection_id),
@@ -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;
@@ -1256,7 +1256,7 @@ mod tests {
//
// TODO: without closing, the opened items do not propagate their history changes for some reason
// it does work in real app though, only tests do not propagate.
- workspace.update(cx, |_, cx| dbg!(cx.focused()));
+ workspace.update(cx, |_, cx| cx.focused());
let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
assert!(
@@ -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> {
@@ -166,7 +166,6 @@ impl TextState {
runs: Option<Vec<TextRun>>,
cx: &mut WindowContext,
) -> LayoutId {
- let text_system = cx.text_system().clone();
let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let line_height = text_style
@@ -174,18 +173,16 @@ impl TextState {
.to_pixels(font_size.into(), cx.rem_size());
let text = SharedString::from(text);
- let rem_size = cx.rem_size();
-
let runs = if let Some(runs) = runs {
runs
} else {
vec![text_style.to_run(text.len())]
};
- let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
+ let layout_id = cx.request_measured_layout(Default::default(), {
let element_state = self.clone();
- move |known_dimensions, available_space| {
+ move |known_dimensions, available_space, cx| {
let wrap_width = if text_style.white_space == WhiteSpace::Normal {
known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
@@ -203,7 +200,8 @@ impl TextState {
}
}
- let Some(lines) = text_system
+ let Some(lines) = cx
+ .text_system()
.shape_text(
&text, font_size, &runs, wrap_width, // Wrap if we know the width.
)
@@ -109,7 +109,6 @@ impl Element for UniformList {
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let max_items = self.item_count;
- let rem_size = cx.rem_size();
let item_size = state
.as_ref()
.map(|s| s.item_size)
@@ -120,9 +119,7 @@ impl Element for UniformList {
.layout(state.map(|s| s.interactive), cx, |style, cx| {
cx.request_measured_layout(
style,
- rem_size,
- move |known_dimensions: Size<Option<Pixels>>,
- available_space: Size<AvailableSpace>| {
+ move |known_dimensions, available_space, _cx| {
let desired_height = item_size.height * max_items;
let width =
known_dimensions
@@ -655,6 +655,20 @@ pub struct Corners<T: Clone + Default + Debug> {
pub bottom_left: T,
}
+impl<T> Corners<T>
+where
+ T: Clone + Default + Debug,
+{
+ pub fn all(value: T) -> Self {
+ Self {
+ top_left: value.clone(),
+ top_right: value.clone(),
+ bottom_right: value.clone(),
+ bottom_left: value,
+ }
+ }
+}
+
impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = size.width.max(size.height) / 2.;
@@ -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 {
@@ -1,4 +1,7 @@
-use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
+use crate::{
+ AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style,
+ WindowContext,
+};
use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::fmt::Debug;
@@ -9,13 +12,21 @@ use taffy::{
Taffy,
};
-type Measureable = dyn Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync;
-
pub struct TaffyLayoutEngine {
- taffy: Taffy<Box<Measureable>>,
+ taffy: Taffy,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: HashSet<LayoutId>,
+ nodes_to_measure: HashMap<
+ LayoutId,
+ Box<
+ dyn FnMut(
+ Size<Option<Pixels>>,
+ Size<AvailableSpace>,
+ &mut WindowContext,
+ ) -> Size<Pixels>,
+ >,
+ >,
}
static EXPECT_MESSAGE: &'static str =
@@ -28,6 +39,7 @@ impl TaffyLayoutEngine {
children_to_parents: HashMap::default(),
absolute_layout_bounds: HashMap::default(),
computed_layouts: HashSet::default(),
+ nodes_to_measure: HashMap::default(),
}
}
@@ -36,6 +48,7 @@ impl TaffyLayoutEngine {
self.children_to_parents.clear();
self.absolute_layout_bounds.clear();
self.computed_layouts.clear();
+ self.nodes_to_measure.clear();
}
pub fn request_layout(
@@ -65,18 +78,18 @@ impl TaffyLayoutEngine {
&mut self,
style: Style,
rem_size: Pixels,
- measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
- + Send
- + Sync
+ measure: impl FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+ 'static,
) -> LayoutId {
let style = style.to_taffy(rem_size);
- let measurable = Box::new(measure);
- self.taffy
- .new_leaf_with_context(style, measurable)
+ let layout_id = self
+ .taffy
+ .new_leaf_with_context(style, ())
.expect(EXPECT_MESSAGE)
- .into()
+ .into();
+ self.nodes_to_measure.insert(layout_id, Box::new(measure));
+ layout_id
}
// Used to understand performance
@@ -126,7 +139,12 @@ impl TaffyLayoutEngine {
Ok(edges)
}
- pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
+ pub fn compute_layout(
+ &mut self,
+ id: LayoutId,
+ available_space: Size<AvailableSpace>,
+ cx: &mut WindowContext,
+ ) {
// Leaving this here until we have a better instrumentation approach.
// println!("Laying out {} children", self.count_all_children(id)?);
// println!("Max layout depth: {}", self.max_depth(0, id)?);
@@ -159,8 +177,8 @@ impl TaffyLayoutEngine {
.compute_layout_with_measure(
id.into(),
available_space.into(),
- |known_dimensions, available_space, _node_id, context| {
- let Some(measure) = context else {
+ |known_dimensions, available_space, node_id, _context| {
+ let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
return taffy::geometry::Size::default();
};
@@ -169,10 +187,11 @@ impl TaffyLayoutEngine {
height: known_dimensions.height.map(Pixels),
};
- measure(known_dimensions, available_space.into()).into()
+ measure(known_dimensions, available_space.into(), cx).into()
},
)
.expect(EXPECT_MESSAGE);
+
// println!("compute_layout took {:?}", started_at.elapsed());
}
@@ -209,9 +209,7 @@ impl AnyView {
) {
cx.with_absolute_element_offset(origin, |cx| {
let (layout_id, rendered_element) = (self.layout)(self, cx);
- cx.window
- .layout_engine
- .compute_layout(layout_id, available_space);
+ cx.compute_layout(layout_id, available_space);
(self.paint)(self, rendered_element, cx);
})
}
@@ -240,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)
}
}
@@ -209,7 +209,7 @@ pub struct Window {
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
viewport_size: Size<Pixels>,
- pub(crate) layout_engine: TaffyLayoutEngine,
+ layout_engine: Option<TaffyLayoutEngine>,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
pub(crate) previous_frame: Frame,
@@ -327,7 +327,7 @@ impl Window {
sprite_atlas,
rem_size: px(16.),
viewport_size: content_size,
- layout_engine: TaffyLayoutEngine::new(),
+ layout_engine: Some(TaffyLayoutEngine::new()),
root_view: None,
element_id_stack: GlobalElementId::default(),
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
@@ -606,9 +606,11 @@ impl<'a> WindowContext<'a> {
self.app.layout_id_buffer.extend(children.into_iter());
let rem_size = self.rem_size();
- self.window
- .layout_engine
- .request_layout(style, rem_size, &self.app.layout_id_buffer)
+ self.window.layout_engine.as_mut().unwrap().request_layout(
+ style,
+ rem_size,
+ &self.app.layout_id_buffer,
+ )
}
/// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
@@ -618,22 +620,25 @@ impl<'a> WindowContext<'a> {
/// The given closure is invoked at layout time with the known dimensions and available space and
/// returns a `Size`.
pub fn request_measured_layout<
- F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
+ F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+ + 'static,
>(
&mut self,
style: Style,
- rem_size: Pixels,
measure: F,
) -> LayoutId {
+ let rem_size = self.rem_size();
self.window
.layout_engine
+ .as_mut()
+ .unwrap()
.request_measured_layout(style, rem_size, measure)
}
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
- self.window
- .layout_engine
- .compute_layout(layout_id, available_space)
+ let mut layout_engine = self.window.layout_engine.take().unwrap();
+ layout_engine.compute_layout(layout_id, available_space, self);
+ self.window.layout_engine = Some(layout_engine);
}
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
@@ -643,6 +648,8 @@ impl<'a> WindowContext<'a> {
let mut bounds = self
.window
.layout_engine
+ .as_mut()
+ .unwrap()
.layout_bounds(layout_id)
.map(Into::into);
bounds.origin += self.element_offset();
@@ -678,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()
@@ -1189,7 +1200,7 @@ impl<'a> WindowContext<'a> {
self.text_system().start_frame();
let window = &mut *self.window;
- window.layout_engine.clear();
+ window.layout_engine.as_mut().unwrap().clear();
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
@@ -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(
@@ -1,3 +1,4 @@
+mod auto_height_editor;
mod focus;
mod kitchen_sink;
mod picker;
@@ -5,6 +6,7 @@ mod scroll;
mod text;
mod z_index;
+pub use auto_height_editor::*;
pub use focus::*;
pub use kitchen_sink::*;
pub use picker::*;
@@ -0,0 +1,34 @@
+use editor::Editor;
+use gpui::{
+ div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+ WindowContext,
+};
+
+pub struct AutoHeightEditorStory {
+ editor: View<Editor>,
+}
+
+impl AutoHeightEditorStory {
+ pub fn new(cx: &mut WindowContext) -> View<Self> {
+ cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+ cx.build_view(|cx| Self {
+ editor: cx.build_view(|cx| {
+ let mut editor = Editor::auto_height(3, cx);
+ editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+ editor
+ }),
+ })
+ }
+}
+
+impl Render for AutoHeightEditorStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ .size_full()
+ .bg(white())
+ .text_sm()
+ .child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
+ }
+}
@@ -12,6 +12,7 @@ use ui::prelude::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
#[strum(serialize_all = "snake_case")]
pub enum ComponentStory {
+ AutoHeightEditor,
Avatar,
Button,
Checkbox,
@@ -23,6 +24,7 @@ pub enum ComponentStory {
Keybinding,
Label,
List,
+ ListHeader,
ListItem,
Scroll,
Text,
@@ -33,6 +35,7 @@ pub enum ComponentStory {
impl ComponentStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
+ Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
@@ -44,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>,
@@ -662,19 +662,19 @@ pub trait FollowableEvents {
pub trait FollowableItem: Item {
type FollowableEvent: FollowableEvents;
fn remote_id(&self) -> Option<ViewId>;
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
fn from_state_proto(
pane: View<Pane>,
project: View<Workspace>,
id: ViewId,
state: &mut Option<proto::view::Variant>,
- cx: &mut AppContext,
+ cx: &mut WindowContext,
) -> Option<Task<Result<View<Self>>>>;
fn add_event_to_update_proto(
&self,
event: &Self::FollowableEvent,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool;
fn apply_update_proto(
&mut self,
@@ -682,20 +682,20 @@ pub trait FollowableItem: Item {
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>>;
- fn is_project_item(&self, cx: &AppContext) -> bool;
+ fn is_project_item(&self, cx: &WindowContext) -> bool;
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
}
pub trait FollowableItemHandle: ItemHandle {
- fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+ fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
fn add_event_to_update_proto(
&self,
event: &dyn Any,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool;
fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
fn apply_update_proto(
@@ -704,11 +704,11 @@ pub trait FollowableItemHandle: ItemHandle {
message: proto::update_view::Variant,
cx: &mut WindowContext,
) -> Task<Result<()>>;
- fn is_project_item(&self, cx: &AppContext) -> bool;
+ fn is_project_item(&self, cx: &WindowContext) -> bool;
}
impl<T: FollowableItem> FollowableItemHandle for View<T> {
- fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+ fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
self.read(cx).remote_id().or_else(|| {
client.peer_id().map(|creator| ViewId {
creator,
@@ -721,7 +721,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
}
- fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
self.read(cx).to_state_proto(cx)
}
@@ -729,7 +729,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
&self,
event: &dyn Any,
update: &mut Option<proto::update_view::Variant>,
- cx: &AppContext,
+ cx: &WindowContext,
) -> bool {
if let Some(event) = event.downcast_ref() {
self.read(cx).add_event_to_update_proto(event, update, cx)
@@ -754,305 +754,315 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
}
- fn is_project_item(&self, cx: &AppContext) -> bool {
+ fn is_project_item(&self, cx: &WindowContext) -> bool {
self.read(cx).is_project_item(cx)
}
}
-// #[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)))
+ }
+ }
+}
@@ -13,9 +13,9 @@ use gpui::{
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,
@@ -507,6 +507,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());
}
@@ -1556,12 +1578,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()),
),
),
@@ -2089,18 +2119,14 @@ impl Render for Pane {
this.update(cx, |this, cx| this.focus_out(cx)).ok();
}
})
- .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)))
.on_action(cx.listener(Pane::toggle_zoom))
.on_action(cx.listener(|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;
@@ -247,7 +247,7 @@ type FollowableItemBuilder = fn(
View<Workspace>,
ViewId,
&mut Option<proto::view::Variant>,
- &mut AppContext,
+ &mut WindowContext,
) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
type FollowableItemBuilders = HashMap<
TypeId,
@@ -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>) {
@@ -3592,7 +3590,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.")
@@ -4473,960 +4471,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 {
@@ -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) {}
@@ -148,7 +148,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);
@@ -157,7 +157,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);