From 781a95d2e3e06d028f35c047b05cf023a4762049 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Nov 2023 14:47:45 +0100 Subject: [PATCH 01/27] Add back Completion::documentation --- crates/language2/src/buffer.rs | 41 ++++++++++++++++++++++++++++++ crates/language2/src/proto.rs | 1 + crates/project2/src/lsp_command.rs | 25 +++++++++++++++--- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 2c8c55d577..cd77dc5859 100644 --- a/crates/language2/src/buffer.rs +++ b/crates/language2/src/buffer.rs @@ -7,6 +7,7 @@ pub use crate::{ use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, + markdown::parse_markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -144,12 +145,52 @@ pub struct Diagnostic { pub is_unnecessary: bool, } +pub async fn prepare_completion_documentation( + documentation: &lsp::Documentation, + language_registry: &Arc, + language: Option>, +) -> Documentation { + match documentation { + lsp::Documentation::String(text) => { + if text.lines().count() <= 1 { + Documentation::SingleLine(text.clone()) + } else { + Documentation::MultiLinePlainText(text.clone()) + } + } + + lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { + lsp::MarkupKind::PlainText => { + if value.lines().count() <= 1 { + Documentation::SingleLine(value.clone()) + } else { + Documentation::MultiLinePlainText(value.clone()) + } + } + + lsp::MarkupKind::Markdown => { + let parsed = parse_markdown(value, language_registry, language).await; + Documentation::MultiLineMarkdown(parsed) + } + }, + } +} + +#[derive(Clone, Debug)] +pub enum Documentation { + Undocumented, + SingleLine(String), + MultiLinePlainText(String), + MultiLineMarkdown(ParsedMarkdown), +} + #[derive(Clone, Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, pub server_id: LanguageServerId, + pub documentation: Option, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language2/src/proto.rs b/crates/language2/src/proto.rs index c4abe39d47..957f4ee7fb 100644 --- a/crates/language2/src/proto.rs +++ b/crates/language2/src/proto.rs @@ -482,6 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), + documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index cc1821d3ff..94c277db1e 100644 --- a/crates/project2/src/lsp_command.rs +++ b/crates/project2/src/lsp_command.rs @@ -10,7 +10,7 @@ use futures::future; use gpui::{AppContext, AsyncAppContext, Model}; use language::{ language_settings::{language_settings, InlayHintKind}, - point_from_lsp, point_to_lsp, + point_from_lsp, point_to_lsp, prepare_completion_documentation, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, @@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, completions: Option, - _: Model, + project: Model, buffer: Model, server_id: LanguageServerId, mut cx: AsyncAppContext, @@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions { Default::default() }; - let completions = buffer.update(&mut cx, |buffer, _| { + let completions = buffer.update(&mut cx, |buffer, cx| { + let language_registry = project.read(cx).languages().clone(); let language = buffer.language().cloned(); let snapshot = buffer.snapshot(); let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); @@ -1443,14 +1444,29 @@ impl LspCommand for GetCompletions { } }; + let language_registry = language_registry.clone(); let language = language.clone(); LineEnding::normalize(&mut new_text); Some(async move { let mut label = None; - if let Some(language) = language { + if let Some(language) = language.as_ref() { language.process_completion(&mut lsp_completion).await; label = language.label_for_completion(&lsp_completion).await; } + + let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { + Some( + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await, + ) + } else { + None + }; + Completion { old_range, new_text, @@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), + documentation, server_id, lsp_completion, } From c08ce1c3b86e95603da73d226c0945278b03ac17 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 16 Nov 2023 14:55:06 +0100 Subject: [PATCH 02/27] Start rendering autocompletion menu --- crates/editor2/src/editor.rs | 477 +++++++++++++++++------------------ 1 file changed, 233 insertions(+), 244 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 8e7bd5876f..48c61c0f32 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -54,13 +54,13 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape, - Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName, - OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, + CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, LanguageRegistry, + LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, }; use lazy_static::lazy_static; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; -use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; +use lsp::{DiagnosticSeverity, LanguageServerId}; use movement::TextLayoutDetails; use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ @@ -97,7 +97,7 @@ use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; -use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip}; +use ui::{h_stack, v_stack, HighlightedLabel, IconButton, StyledExt, Tooltip}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ item::{ItemEvent, ItemHandle}, @@ -1224,208 +1224,196 @@ impl CompletionsMenu { workspace: Option>, cx: &mut ViewContext, ) -> AnyElement { - todo!("old implementation below") + // enum CompletionTag {} + + let settings = EditorSettings::get_global(cx); + let show_completion_documentation = settings.show_completion_documentation; + + let widest_completion_ix = self + .matches + .iter() + .enumerate() + .max_by_key(|(_, mat)| { + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; + let documentation = &completion.documentation; + + let mut len = completion.label.text.chars().count(); + if let Some(Documentation::SingleLine(text)) = documentation { + if show_completion_documentation { + len += text.chars().count(); + } + } + + len + }) + .map(|(ix, _)| ix); + + let completions = self.completions.clone(); + let matches = self.matches.clone(); + let selected_item = self.selected_item; + + let list = uniform_list("completions", matches.len(), move |editor, range, cx| { + let start_ix = range.start; + let completions_guard = completions.read(); + + matches[range] + .iter() + .enumerate() + .map(|(ix, mat)| { + let item_ix = start_ix + ix; + let candidate_id = mat.candidate_id; + let completion = &completions_guard[candidate_id]; + + let documentation = if show_completion_documentation { + &completion.documentation + } else { + &None + }; + + // todo!("highlights") + // let highlights = combine_syntax_and_fuzzy_match_highlights( + // &completion.label.text, + // style.text.color.into(), + // styled_runs_for_code_label(&completion.label, &style.syntax), + // &mat.positions, + // ) + + // todo!("documentation") + // MouseEventHandler::new::(mat.candidate_id, cx, |state, _| { + // let completion_label = HighlightedLabel::new( + // completion.label.text.clone(), + // combine_syntax_and_fuzzy_match_highlights( + // &completion.label.text, + // style.text.color.into(), + // styled_runs_for_code_label(&completion.label, &style.syntax), + // &mat.positions, + // ), + // ); + // Text::new(completion.label.text.clone(), style.text.clone()) + // .with_soft_wrap(false) + // .with_highlights(); + + // if let Some(Documentation::SingleLine(text)) = documentation { + // h_stack() + // .child(completion_label) + // .with_children((|| { + // let text_style = TextStyle { + // color: style.autocomplete.inline_docs_color, + // font_size: style.text.font_size + // * style.autocomplete.inline_docs_size_percent, + // ..style.text.clone() + // }; + + // let label = Text::new(text.clone(), text_style) + // .aligned() + // .constrained() + // .dynamically(move |constraint, _, _| gpui::SizeConstraint { + // min: constraint.min, + // max: vec2f(constraint.max.x(), constraint.min.y()), + // }); + + // if Some(item_ix) == widest_completion_ix { + // Some( + // label + // .contained() + // .with_style(style.autocomplete.inline_docs_container) + // .into_any(), + // ) + // } else { + // Some(label.flex_float().into_any()) + // } + // })()) + // .into_any() + // } else { + // completion_label.into_any() + // } + // .contained() + // .with_style(item_style) + // .constrained() + // .dynamically(move |constraint, _, _| { + // if Some(item_ix) == widest_completion_ix { + // constraint + // } else { + // gpui::SizeConstraint { + // min: constraint.min, + // max: constraint.min, + // } + // } + // }) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_down(MouseButton::Left, move |_, this, cx| { + // this.confirm_completion( + // &ConfirmCompletion { + // item_ix: Some(item_ix), + // }, + // cx, + // ) + // .map(|task| task.detach()); + // }) + // .constrained() + // + div() + .id(mat.candidate_id) + .bg(gpui::green()) + .hover(|style| style.bg(gpui::blue())) + .when(item_ix == selected_item, |div| div.bg(gpui::blue())) + .child(completion.label.text.clone()) + .min_w(px(300.)) + .max_w(px(700.)) + }) + .collect() + }) + .with_width_from_item(widest_completion_ix); + + list.render() + // todo!("multiline documentation") + // enum MultiLineDocumentation {} + + // Flex::row() + // .with_child(list.flex(1., false)) + // .with_children({ + // let mat = &self.matches[selected_item]; + // let completions = self.completions.read(); + // let completion = &completions[mat.candidate_id]; + // let documentation = &completion.documentation; + + // match documentation { + // Some(Documentation::MultiLinePlainText(text)) => Some( + // Flex::column() + // .scrollable::(0, None, cx) + // .with_child( + // Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), + // ) + // .contained() + // .with_style(style.autocomplete.alongside_docs_container) + // .constrained() + // .with_max_width(style.autocomplete.alongside_docs_max_width) + // .flex(1., false), + // ), + + // Some(Documentation::MultiLineMarkdown(parsed)) => Some( + // Flex::column() + // .scrollable::(0, None, cx) + // .with_child(render_parsed_markdown::( + // parsed, &style, workspace, cx, + // )) + // .contained() + // .with_style(style.autocomplete.alongside_docs_container) + // .constrained() + // .with_max_width(style.autocomplete.alongside_docs_max_width) + // .flex(1., false), + // ), + + // _ => None, + // } + // }) + // .contained() + // .with_style(style.autocomplete.container) + // .into_any() } - // enum CompletionTag {} - - // let settings = EditorSettings>(cx); - // let show_completion_documentation = settings.show_completion_documentation; - - // let widest_completion_ix = self - // .matches - // .iter() - // .enumerate() - // .max_by_key(|(_, mat)| { - // let completions = self.completions.read(); - // let completion = &completions[mat.candidate_id]; - // let documentation = &completion.documentation; - - // let mut len = completion.label.text.chars().count(); - // if let Some(Documentation::SingleLine(text)) = documentation { - // if show_completion_documentation { - // len += text.chars().count(); - // } - // } - - // len - // }) - // .map(|(ix, _)| ix); - - // let completions = self.completions.clone(); - // let matches = self.matches.clone(); - // let selected_item = self.selected_item; - - // let list = UniformList::new(self.list.clone(), matches.len(), cx, { - // let style = style.clone(); - // move |_, range, items, cx| { - // let start_ix = range.start; - // let completions_guard = completions.read(); - - // for (ix, mat) in matches[range].iter().enumerate() { - // let item_ix = start_ix + ix; - // let candidate_id = mat.candidate_id; - // let completion = &completions_guard[candidate_id]; - - // let documentation = if show_completion_documentation { - // &completion.documentation - // } else { - // &None - // }; - - // items.push( - // MouseEventHandler::new::( - // mat.candidate_id, - // cx, - // |state, _| { - // let item_style = if item_ix == selected_item { - // style.autocomplete.selected_item - // } else if state.hovered() { - // style.autocomplete.hovered_item - // } else { - // style.autocomplete.item - // }; - - // let completion_label = - // Text::new(completion.label.text.clone(), style.text.clone()) - // .with_soft_wrap(false) - // .with_highlights( - // combine_syntax_and_fuzzy_match_highlights( - // &completion.label.text, - // style.text.color.into(), - // styled_runs_for_code_label( - // &completion.label, - // &style.syntax, - // ), - // &mat.positions, - // ), - // ); - - // if let Some(Documentation::SingleLine(text)) = documentation { - // Flex::row() - // .with_child(completion_label) - // .with_children((|| { - // let text_style = TextStyle { - // color: style.autocomplete.inline_docs_color, - // font_size: style.text.font_size - // * style.autocomplete.inline_docs_size_percent, - // ..style.text.clone() - // }; - - // let label = Text::new(text.clone(), text_style) - // .aligned() - // .constrained() - // .dynamically(move |constraint, _, _| { - // gpui::SizeConstraint { - // min: constraint.min, - // max: vec2f( - // constraint.max.x(), - // constraint.min.y(), - // ), - // } - // }); - - // if Some(item_ix) == widest_completion_ix { - // Some( - // label - // .contained() - // .with_style( - // style - // .autocomplete - // .inline_docs_container, - // ) - // .into_any(), - // ) - // } else { - // Some(label.flex_float().into_any()) - // } - // })()) - // .into_any() - // } else { - // completion_label.into_any() - // } - // .contained() - // .with_style(item_style) - // .constrained() - // .dynamically( - // move |constraint, _, _| { - // if Some(item_ix) == widest_completion_ix { - // constraint - // } else { - // gpui::SizeConstraint { - // min: constraint.min, - // max: constraint.min, - // } - // } - // }, - // ) - // }, - // ) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_down(MouseButton::Left, move |_, this, cx| { - // this.confirm_completion( - // &ConfirmCompletion { - // item_ix: Some(item_ix), - // }, - // cx, - // ) - // .map(|task| task.detach()); - // }) - // .constrained() - // .with_min_width(style.autocomplete.completion_min_width) - // .with_max_width(style.autocomplete.completion_max_width) - // .into_any(), - // ); - // } - // } - // }) - // .with_width_from_item(widest_completion_ix); - - // enum MultiLineDocumentation {} - - // Flex::row() - // .with_child(list.flex(1., false)) - // .with_children({ - // let mat = &self.matches[selected_item]; - // let completions = self.completions.read(); - // let completion = &completions[mat.candidate_id]; - // let documentation = &completion.documentation; - - // match documentation { - // Some(Documentation::MultiLinePlainText(text)) => Some( - // Flex::column() - // .scrollable::(0, None, cx) - // .with_child( - // Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), - // ) - // .contained() - // .with_style(style.autocomplete.alongside_docs_container) - // .constrained() - // .with_max_width(style.autocomplete.alongside_docs_max_width) - // .flex(1., false), - // ), - - // Some(Documentation::MultiLineMarkdown(parsed)) => Some( - // Flex::column() - // .scrollable::(0, None, cx) - // .with_child(render_parsed_markdown::( - // parsed, &style, workspace, cx, - // )) - // .contained() - // .with_style(style.autocomplete.alongside_docs_container) - // .constrained() - // .with_max_width(style.autocomplete.alongside_docs_max_width) - // .flex(1., false), - // ), - - // _ => None, - // } - // }) - // .contained() - // .with_style(style.autocomplete.container) - // .into_any() - // } - pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { let mut matches = if let Some(query) = query { fuzzy::match_strings( @@ -10110,49 +10098,50 @@ pub fn combine_syntax_and_fuzzy_match_highlights( result } -// pub fn styled_runs_for_code_label<'a>( -// label: &'a CodeLabel, -// syntax_theme: &'a theme::SyntaxTheme, -// ) -> impl 'a + Iterator, HighlightStyle)> { -// let fade_out = HighlightStyle { -// fade_out: Some(0.35), -// ..Default::default() -// }; +pub fn styled_runs_for_code_label<'a>( + label: &'a CodeLabel, + syntax_theme: &'a theme::SyntaxTheme, +) -> impl 'a + Iterator, HighlightStyle)> { + let fade_out = HighlightStyle { + fade_out: Some(0.35), + ..Default::default() + }; -// let mut prev_end = label.filter_range.end; -// label -// .runs -// .iter() -// .enumerate() -// .flat_map(move |(ix, (range, highlight_id))| { -// let style = if let Some(style) = highlight_id.style(syntax_theme) { -// style -// } else { -// return Default::default(); -// }; -// let mut muted_style = style; -// muted_style.highlight(fade_out); + let mut prev_end = label.filter_range.end; + label + .runs + .iter() + .enumerate() + .flat_map(move |(ix, (range, highlight_id))| { + let style = if let Some(style) = highlight_id.style(syntax_theme) { + style + } else { + return Default::default(); + }; + let mut muted_style = style; + muted_style.highlight(fade_out); -// let mut runs = SmallVec::<[(Range, HighlightStyle); 3]>::new(); -// if range.start >= label.filter_range.end { -// if range.start > prev_end { -// runs.push((prev_end..range.start, fade_out)); -// } -// runs.push((range.clone(), muted_style)); -// } else if range.end <= label.filter_range.end { -// runs.push((range.clone(), style)); -// } else { -// runs.push((range.start..label.filter_range.end, style)); -// runs.push((label.filter_range.end..range.end, muted_style)); -// } -// prev_end = cmp::max(prev_end, range.end); + let mut runs = SmallVec::<[(Range, HighlightStyle); 3]>::new(); + if range.start >= label.filter_range.end { + if range.start > prev_end { + runs.push((prev_end..range.start, fade_out)); + } + runs.push((range.clone(), muted_style)); + } else if range.end <= label.filter_range.end { + runs.push((range.clone(), style)); + } else { + runs.push((range.start..label.filter_range.end, style)); + runs.push((label.filter_range.end..range.end, muted_style)); + } + prev_end = cmp::max(prev_end, range.end); -// if ix + 1 == label.runs.len() && label.text.len() > prev_end { -// runs.push((prev_end..label.text.len(), fade_out)); -// } + if ix + 1 == label.runs.len() && label.text.len() > prev_end { + runs.push((prev_end..label.text.len(), fade_out)); + } -// runs -// }) + runs + }) +} pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { let mut index = 0; From 170291ff96baefded554a3524e64fe2681621d5b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:57:24 +0100 Subject: [PATCH 03/27] Start decoupling workspace and call crates --- Cargo.lock | 1 + crates/workspace2/Cargo.toml | 1 + crates/workspace2/src/pane_group.rs | 7 +- crates/workspace2/src/workspace2.rs | 382 ++++++++++++++++++---------- 4 files changed, 246 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aa94b08d0..85f474b046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11319,6 +11319,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-recursion 1.0.5", + "async-trait", "bincode", "call2", "client2", diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index f3f10d2015..bddf019eb5 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -37,6 +37,7 @@ theme2 = { path = "../theme2" } util = { path = "../util" } ui = { package = "ui2", path = "../ui2" } +async-trait.workspace = true async-recursion = "1.0.0" itertools = "0.10" bincode = "1.2.1" diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index bd827a6dd7..80e002a429 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -127,7 +127,6 @@ impl PaneGroup { &self, project: &Model, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -137,7 +136,6 @@ impl PaneGroup { project, 0, follower_states, - active_call, active_pane, zoomed, app_state, @@ -199,7 +197,6 @@ impl Member { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -234,7 +231,6 @@ impl Member { project, basis + 1, follower_states, - active_call, active_pane, zoomed, app_state, @@ -556,7 +552,7 @@ impl PaneAxis { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, + active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -578,7 +574,6 @@ impl PaneAxis { project, basis, follower_states, - active_call, active_pane, zoomed, app_state, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 22a7b57058..64f6e5963d 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -15,7 +15,8 @@ mod status_bar; mod toolbar; mod workspace_settings; -use anyhow::{anyhow, Context as _, Result}; +use anyhow::{anyhow, bail, Context as _, Result}; +use async_trait::async_trait; use call2::ActiveCall; use client2::{ proto::{self, PeerId}, @@ -33,8 +34,8 @@ use gpui::{ AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, - View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, - WindowOptions, + View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, + WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -408,6 +409,177 @@ pub enum Event { WorkspaceCreated(WeakView), } +#[async_trait(?Send)] +trait CallHandler { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option>; + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState>; + fn follower_states(&self) -> &HashMap, FollowerState>; + fn room_id(&self, cx: &AppContext) -> Option; + fn is_in_room(&self, cx: &mut ViewContext) -> bool { + self.room_id(cx).is_some() + } + fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; + fn active_project(&self, cx: &AppContext) -> Option>; +} +struct Call { + follower_states: HashMap, FollowerState>, + active_call: Option<(Model, Vec)>, + parent_workspace: WeakView, +} + +impl Call { + fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; + active_call = Some((call, subscriptions)); + } + Self { + follower_states: Default::default(), + active_call, + parent_workspace, + } + } + fn on_active_call_event( + workspace: &mut Workspace, + _: Model, + event: &call2::room::Event, + cx: &mut ViewContext, + ) { + match event { + call2::room::Event::ParticipantLocationChanged { participant_id } + | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + workspace.leader_updated(*participant_id, cx); + } + _ => {} + } + } +} + +#[async_trait(?Send)] +impl CallHandler for Call { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + let mut items_to_activate = Vec::new(); + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call2::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project.read(cx).remote_id()) + .log_err() + .flatten(); + } + call2::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call2::ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(peer_id)?; + let track = participant.video_tracks.values().next()?.clone(); + let user = participant.user.clone(); + todo!(); + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Box::new(Some(item)); + // } + // } + + // Some(Box::new(cx.build_view(|cx| { + // SharedScreen::new(&track, peer_id, user.clone(), cx) + // }))) + } + + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { + &mut self.follower_states + } + fn follower_states(&self) -> &HashMap, FollowerState> { + &self.follower_states + } + fn room_id(&self, cx: &AppContext) -> Option { + Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) + } + fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { + let Some((call, _)) = self.active_call.as_ref() else { + bail!("Cannot exit a call; not in a call"); + }; + + call.update(&mut cx, |this, cx| this.hang_up(cx)) + } + fn active_project(&self, cx: &AppContext) -> Option> { + ActiveCall::global(cx).read(cx).location().cloned() + } +} pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -428,10 +600,9 @@ pub struct Workspace { titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, - follower_states: HashMap, FollowerState>, + call_handler: Box, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, - active_call: Option<(Model, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, @@ -550,9 +721,19 @@ impl Workspace { mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); let _apply_leader_updates = cx.spawn(|this, mut cx| async move { while let Some((leader_id, update)) = leader_updates_rx.next().await { - Self::process_leader_update(&this, leader_id, update, &mut cx) + let mut cx2 = cx.clone(); + let t = this.clone(); + + Workspace::process_leader_update(&this, leader_id, update, &mut cx) .await .log_err(); + + // this.update(&mut cx, |this, cxx| { + // this.call_handler + // .process_leader_update(leader_id, update, cx2) + // })? + // .await + // .log_err(); } Ok(()) @@ -585,14 +766,6 @@ impl Workspace { // drag_and_drop.register_container(weak_handle.clone()); // }); - let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); - let mut subscriptions = Vec::new(); - subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); - active_call = Some((call, subscriptions)); - } - let subscriptions = vec![ cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, cx| { @@ -652,10 +825,11 @@ impl Workspace { bottom_dock, right_dock, project: project.clone(), - follower_states: Default::default(), + last_leaders_by_pane: Default::default(), window_edited: false, - active_call, + + call_handler: Box::new(Call::new(weak_handle.clone(), cx)), database_id: workspace_id, app_state, _observe_current_user, @@ -1102,7 +1276,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { //todo!(saveing) - let active_call = self.active_call().cloned(); + let window = cx.window_handle(); cx.spawn(|this, mut cx| async move { @@ -1113,27 +1287,27 @@ impl Workspace { .count() })?; - if let Some(active_call) = active_call { - if !quitting - && workspace_count == 1 - && active_call.read_with(&cx, |call, _| call.room().is_some())? - { - let answer = window.update(&mut cx, |_, cx| { - cx.prompt( - PromptLevel::Warning, - "Do you want to leave the current call?", - &["Close window and hang up", "Cancel"], - ) - })?; + if !quitting + && workspace_count == 1 + && this + .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx)) + .log_err() + .unwrap_or_default() + { + let answer = window.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Do you want to leave the current call?", + &["Close window and hang up", "Cancel"], + ) + })?; - if answer.await.log_err() == Some(1) { - return anyhow::Ok(false); - } else { - active_call - .update(&mut cx, |call, cx| call.hang_up(cx))? - .await - .log_err(); - } + if answer.await.log_err() == Some(1) { + return anyhow::Ok(false); + } else { + this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))?? + .await + .log_err(); } } @@ -2238,7 +2412,7 @@ impl Workspace { } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - self.follower_states.retain(|_, state| { + self.call_handler.follower_states_mut().retain(|_, state| { if state.leader_id == peer_id { for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); @@ -2391,19 +2565,19 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let state = self.follower_states.remove(pane)?; + let follower_states = self.call_handler.follower_states_mut(); + let state = follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { item.set_leader_peer_id(None, cx); } - if self - .follower_states + if follower_states .values() .all(|state| state.leader_id != state.leader_id) { let project_id = self.project.read(cx).remote_id(); - let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + let room_id = self.call_handler.room_id(cx)?; self.app_state .client .send(proto::Unfollow { @@ -2614,7 +2788,7 @@ impl Workspace { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { this.update(cx, |this, _| { - for (_, state) in &mut this.follower_states { + for (_, state) in this.call_handler.follower_states_mut() { if state.leader_id == leader_id { state.active_view_id = if let Some(active_view_id) = update_active_view.id.clone() { @@ -2637,7 +2811,7 @@ impl Workspace { let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); - for (_, state) in &mut this.follower_states { + for (_, state) in this.call_handler.follower_states_mut() { if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { @@ -2651,7 +2825,8 @@ impl Workspace { } proto::update_followers::Variant::CreateView(view) => { let panes = this.update(cx, |this, _| { - this.follower_states + this.call_handler + .follower_states() .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() @@ -2711,7 +2886,7 @@ impl Workspace { for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { let items = futures::future::try_join_all(item_tasks).await?; this.update(cx, |this, cx| { - let state = this.follower_states.get_mut(&pane)?; + let state = this.call_handler.follower_states_mut().get_mut(&pane)?; for (id, item) in leader_view_ids.into_iter().zip(items) { item.set_leader_peer_id(Some(leader_id), cx); state.items_by_leader_view_id.insert(id, item); @@ -2768,74 +2943,14 @@ impl Workspace { } pub fn leader_for_pane(&self, pane: &View) -> Option { - self.follower_states.get(pane).map(|state| state.leader_id) + self.call_handler + .follower_states() + .get(pane) + .map(|state| state.leader_id) } fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let call = self.active_call()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, cx| { - pane.add_item(item.boxed_clone(), false, false, None, cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None + self.call_handler.leader_updated(leader_id, cx) } // todo!() @@ -2886,25 +3001,6 @@ impl Workspace { } } - fn active_call(&self) -> Option<&Model> { - self.active_call.as_ref().map(|(call, _)| call) - } - - fn on_active_call_event( - &mut self, - _: Model, - event: &call2::room::Event, - cx: &mut ViewContext, - ) { - match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { - self.leader_updated(*participant_id, cx); - } - _ => {} - } - } - pub fn database_id(&self) -> WorkspaceId { self.database_id } @@ -3671,8 +3767,7 @@ impl Render for Workspace { .flex_1() .child(self.center.render( &self.project, - &self.follower_states, - self.active_call(), + &self.call_handler.follower_states(), &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -3845,11 +3940,12 @@ impl WorkspaceStore { update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { - if !cx.has_global::>() { - return None; - } - - let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); + let room_id = self.workspaces.iter().next().and_then(|workspace| { + workspace + .read_with(cx, |this, cx| this.call_handler.room_id(cx)) + .log_err() + .flatten() + })?; let follower_ids: Vec<_> = self .followers .iter() @@ -3885,9 +3981,17 @@ impl WorkspaceStore { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; - let active_project = ActiveCall::global(cx).read(cx).location().cloned(); - let mut response = proto::FollowResponse::default(); + let active_project = this + .workspaces + .iter() + .next() + .and_then(|workspace| { + workspace + .read_with(cx, |this, cx| this.call_handler.active_project(cx)) + .log_err() + }) + .flatten(); for workspace in &this.workspaces { workspace .update(cx, |workspace, cx| { From ebccdb64bcf7b62d65d99e3065bb932c599b1d27 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:18:35 +0100 Subject: [PATCH 04/27] Move CallHandler impl into call2 --- Cargo.lock | 1 + crates/call2/Cargo.toml | 2 +- crates/call2/src/call2.rs | 154 +++++++++++++++++++++ crates/workspace2/Cargo.toml | 1 - crates/workspace2/src/pane_group.rs | 1 - crates/workspace2/src/workspace2.rs | 202 ++++++---------------------- 6 files changed, 196 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85f474b046..17bda4458c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,6 +1193,7 @@ dependencies = [ "serde_json", "settings2", "util", + "workspace2", ] [[package]] diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 9e13463680..500931cc11 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -31,7 +31,7 @@ media = { path = "../media" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } - +workspace = {package = "workspace2", path = "../workspace2"} anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 1f11e0650d..34a9aabe14 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -505,6 +505,160 @@ pub fn report_call_event_for_channel( ) } +struct Call { + follower_states: HashMap, FollowerState>, + active_call: Option<(Model, Vec)>, + parent_workspace: WeakView, +} + +impl Call { + fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; + active_call = Some((call, subscriptions)); + } + Self { + follower_states: Default::default(), + active_call, + parent_workspace, + } + } + fn on_active_call_event( + workspace: &mut Workspace, + _: Model, + event: &call2::room::Event, + cx: &mut ViewContext, + ) { + match event { + call2::room::Event::ParticipantLocationChanged { participant_id } + | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + workspace.leader_updated(*participant_id, cx); + } + _ => {} + } + } +} + +#[async_trait(?Send)] +impl CallHandler for Call { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + let mut items_to_activate = Vec::new(); + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call2::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project.read(cx).remote_id()) + .log_err() + .flatten(); + } + call2::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call2::ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(peer_id)?; + let track = participant.video_tracks.values().next()?.clone(); + let user = participant.user.clone(); + todo!(); + // for item in pane.read(cx).items_of_type::() { + // if item.read(cx).peer_id == peer_id { + // return Box::new(Some(item)); + // } + // } + + // Some(Box::new(cx.build_view(|cx| { + // SharedScreen::new(&track, peer_id, user.clone(), cx) + // }))) + } + + fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { + &mut self.follower_states + } + fn follower_states(&self) -> &HashMap, FollowerState> { + &self.follower_states + } + fn room_id(&self, cx: &AppContext) -> Option { + Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) + } + fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { + let Some((call, _)) = self.active_call.as_ref() else { + bail!("Cannot exit a call; not in a call"); + }; + + call.update(&mut cx, |this, cx| this.hang_up(cx)) + } + fn active_project(&self, cx: &AppContext) -> Option> { + ActiveCall::global(cx).read(cx).location().cloned() + } +} + #[cfg(test)] mod test { use gpui::TestAppContext; diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index bddf019eb5..c327132a78 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -20,7 +20,6 @@ test-support = [ [dependencies] db2 = { path = "../db2" } -call2 = { path = "../call2" } client2 = { path = "../client2" } collections = { path = "../collections" } # context_menu = { path = "../context_menu" } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 80e002a429..eeea0bd365 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,6 +1,5 @@ use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, bail, Result}; -use call2::ActiveCall; use collections::HashMap; use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 64f6e5963d..05e994b74f 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -17,7 +17,6 @@ mod workspace_settings; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; -use call2::ActiveCall; use client2::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, @@ -410,7 +409,7 @@ pub enum Event { } #[async_trait(?Send)] -trait CallHandler { +pub trait CallHandler { fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; fn shared_screen_for_peer( &self, @@ -427,159 +426,7 @@ trait CallHandler { fn hang_up(&self, cx: AsyncWindowContext) -> Result>>; fn active_project(&self, cx: &AppContext) -> Option>; } -struct Call { - follower_states: HashMap, FollowerState>, - active_call: Option<(Model, Vec)>, - parent_workspace: WeakView, -} -impl Call { - fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { - let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); - let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; - active_call = Some((call, subscriptions)); - } - Self { - follower_states: Default::default(), - active_call, - parent_workspace, - } - } - fn on_active_call_event( - workspace: &mut Workspace, - _: Model, - event: &call2::room::Event, - cx: &mut ViewContext, - ) { - match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { - workspace.leader_updated(*participant_id, cx); - } - _ => {} - } - } -} - -#[async_trait(?Send)] -impl CallHandler for Call { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) - == self - .parent_workspace - .update(cx, |this, cx| this.project.read(cx).remote_id()) - .log_err() - .flatten(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, mut cx| { - pane.add_item(item.boxed_clone(), false, false, None, &mut cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None - } - - fn shared_screen_for_peer( - &self, - peer_id: PeerId, - pane: &View, - cx: &mut ViewContext, - ) -> Option> { - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(peer_id)?; - let track = participant.video_tracks.values().next()?.clone(); - let user = participant.user.clone(); - todo!(); - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Box::new(Some(item)); - // } - // } - - // Some(Box::new(cx.build_view(|cx| { - // SharedScreen::new(&track, peer_id, user.clone(), cx) - // }))) - } - - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { - &mut self.follower_states - } - fn follower_states(&self) -> &HashMap, FollowerState> { - &self.follower_states - } - fn room_id(&self, cx: &AppContext) -> Option { - Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) - } - fn hang_up(&self, mut cx: AsyncWindowContext) -> Result>> { - let Some((call, _)) = self.active_call.as_ref() else { - bail!("Cannot exit a call; not in a call"); - }; - - call.update(&mut cx, |this, cx| this.hang_up(cx)) - } - fn active_project(&self, cx: &AppContext) -> Option> { - ActiveCall::global(cx).read(cx).location().cloned() - } -} pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -611,6 +458,7 @@ pub struct Workspace { _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, + call_factory: CallFactory, } impl EventEmitter for Workspace {} @@ -630,11 +478,13 @@ struct FollowerState { enum WorkspaceBounds {} +type CallFactory = fn(WeakView, &mut ViewContext) -> Box; impl Workspace { pub fn new( workspace_id: WorkspaceId, project: Model, app_state: Arc, + call_factory: CallFactory, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); @@ -829,7 +679,7 @@ impl Workspace { last_leaders_by_pane: Default::default(), window_edited: false, - call_handler: Box::new(Call::new(weak_handle.clone(), cx)), + call_handler: call_factory(weak_handle.clone(), cx), database_id: workspace_id, app_state, _observe_current_user, @@ -839,6 +689,7 @@ impl Workspace { subscriptions, pane_history_timestamp, workspace_actions: Default::default(), + call_factory, } } @@ -846,6 +697,7 @@ impl Workspace { abs_paths: Vec, app_state: Arc, requesting_window: Option>, + call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -896,7 +748,13 @@ impl Workspace { let window = if let Some(window) = requesting_window { cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { - Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) + Workspace::new( + workspace_id, + project_handle.clone(), + app_state.clone(), + call_factory, + cx, + ) }); })?; window @@ -942,7 +800,13 @@ impl Workspace { let project_handle = project_handle.clone(); move |cx| { cx.build_view(|cx| { - Workspace::new(workspace_id, project_handle, app_state, cx) + Workspace::new( + workspace_id, + project_handle, + app_state, + call_factory, + cx, + ) }) } })? @@ -1203,7 +1067,13 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(Ok(callback(self, cx)))) } else { - let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + let task = Self::new_local( + Vec::new(), + self.app_state.clone(), + None, + self.call_factory, + cx, + ); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await?; workspace.update(&mut cx, callback) @@ -1432,7 +1302,7 @@ impl Workspace { Some(self.prepare_to_close(false, cx)) }; let app_state = self.app_state.clone(); - + let call_factory = self.call_factory; cx.spawn(|_, mut cx| async move { let window_to_replace = if let Some(close_task) = close_task { if !close_task.await? { @@ -1442,7 +1312,7 @@ impl Workspace { } else { None }; - cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))? + cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, call_factory, cx))? .await?; Ok(()) }) @@ -4331,6 +4201,7 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, requesting_window: Option>, + call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -4357,7 +4228,13 @@ pub fn open_paths( todo!() } else { cx.update(move |cx| { - Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) + Workspace::new_local( + abs_paths, + app_state.clone(), + requesting_window, + call_factory, + cx, + ) })? .await } @@ -4368,8 +4245,9 @@ pub fn open_new( app_state: &Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, + call_factory: CallFactory, ) -> Task<()> { - let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, call_factory, cx); cx.spawn(|mut cx| async move { if let Some((workspace, opened_paths)) = task.await.log_err() { workspace From abe5a9c85f909414d044bc1691af756fd64177be Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:51:53 +0100 Subject: [PATCH 05/27] Finish up decoupling workspace from call --- Cargo.lock | 1 + crates/call2/Cargo.toml | 1 + crates/call2/src/call2.rs | 149 ++++++++++------------------ crates/workspace2/src/workspace2.rs | 126 ++++++++++++----------- crates/zed2/src/main.rs | 2 +- 5 files changed, 126 insertions(+), 153 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17bda4458c..b96e12a0fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,6 +1175,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-broadcast", + "async-trait", "audio2", "client2", "collections", diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 500931cc11..43e19b4ccb 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -32,6 +32,7 @@ project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } workspace = {package = "workspace2", path = "../workspace2"} +async-trait.workspace = true anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 34a9aabe14..18576d4657 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -2,24 +2,29 @@ pub mod call_settings; pub mod participant; pub mod room; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; +use async_trait::async_trait; use audio::Audio; use call_settings::CallSettings; -use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; +use client::{ + proto::{self, PeerId}, + Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, +}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ - AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, - WeakModel, + AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext, + Subscription, Task, View, ViewContext, WeakModel, WeakView, }; +pub use participant::ParticipantLocation; use postage::watch; use project::Project; use room::Event; +pub use room::Room; use settings::Settings; use std::sync::Arc; - -pub use participant::ParticipantLocation; -pub use room::Room; +use util::ResultExt; +use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; pub fn init(client: Arc, user_store: Model, cx: &mut AppContext) { CallSettings::register(cx); @@ -505,35 +510,36 @@ pub fn report_call_event_for_channel( ) } -struct Call { - follower_states: HashMap, FollowerState>, +pub struct Call { active_call: Option<(Model, Vec)>, parent_workspace: WeakView, } impl Call { - fn new(parent_workspace: WeakView, cx: &mut ViewContext<'_, Workspace>) -> Self { + pub fn new( + parent_workspace: WeakView, + cx: &mut ViewContext<'_, Workspace>, + ) -> Box { let mut active_call = None; if cx.has_global::>() { let call = cx.global::>().clone(); let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; active_call = Some((call, subscriptions)); } - Self { - follower_states: Default::default(), + Box::new(Self { active_call, parent_workspace, - } + }) } fn on_active_call_event( workspace: &mut Workspace, _: Model, - event: &call2::room::Event, + event: &room::Event, cx: &mut ViewContext, ) { match event { - call2::room::Event::ParticipantLocationChanged { participant_id } - | call2::room::Event::RemoteVideoTracksChanged { participant_id } => { + room::Event::ParticipantLocationChanged { participant_id } + | room::Event::RemoteVideoTracksChanged { participant_id } => { workspace.leader_updated(*participant_id, cx); } _ => {} @@ -543,78 +549,6 @@ impl Call { #[async_trait(?Send)] impl CallHandler for Call { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - cx.notify(); - - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - let mut items_to_activate = Vec::new(); - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - call2::ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) - == self - .parent_workspace - .update(cx, |this, cx| this.project.read(cx).remote_id()) - .log_err() - .flatten(); - } - call2::ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - call2::ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - for (pane, state) in &self.follower_states { - if state.leader_id != leader_id { - continue; - } - if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { - if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { - if leader_in_this_project || !item.is_project_item(cx) { - items_to_activate.push((pane.clone(), item.boxed_clone())); - } - } else { - log::warn!( - "unknown view id {:?} for leader {:?}", - active_view_id, - leader_id - ); - } - continue; - } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } - } - - for (pane, item) in items_to_activate { - let pane_was_focused = pane.read(cx).has_focus(cx); - if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { - pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); - } else { - pane.update(cx, |pane, mut cx| { - pane.add_item(item.boxed_clone(), false, false, None, &mut cx) - }); - } - - if pane_was_focused { - pane.update(cx, |pane, cx| pane.focus_active_item(cx)); - } - } - - None - } - fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -638,12 +572,6 @@ impl CallHandler for Call { // }))) } - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState> { - &mut self.follower_states - } - fn follower_states(&self) -> &HashMap, FollowerState> { - &self.follower_states - } fn room_id(&self, cx: &AppContext) -> Option { Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) } @@ -657,6 +585,39 @@ impl CallHandler for Call { fn active_project(&self, cx: &AppContext) -> Option> { ActiveCall::global(cx).read(cx).location().cloned() } + fn peer_state( + &mut self, + leader_id: PeerId, + cx: &mut ViewContext, + ) -> Option<(bool, bool)> { + let (call, _) = self.active_call.as_ref()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) + == self + .parent_workspace + .update(cx, |this, cx| this.project().read(cx).remote_id()) + .log_err() + .flatten(); + } + ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + + Some((leader_in_this_project, leader_in_this_app)) + } } #[cfg(test)] diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 05e994b74f..754988a605 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -15,7 +15,7 @@ mod status_bar; mod toolbar; mod workspace_settings; -use anyhow::{anyhow, bail, Context as _, Result}; +use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; use client2::{ proto::{self, PeerId}, @@ -207,10 +207,10 @@ pub fn init_settings(cx: &mut AppContext) { ItemSettings::register(cx); } -pub fn init(app_state: Arc, cx: &mut AppContext) { +pub fn init(app_state: Arc, cx: &mut AppContext, call_factory: CallFactory) { init_settings(cx); notifications::init(cx); - + cx.set_global(call_factory); // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { @@ -410,15 +410,13 @@ pub enum Event { #[async_trait(?Send)] pub trait CallHandler { - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()>; + fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext) -> Option<(bool, bool)>; fn shared_screen_for_peer( &self, peer_id: PeerId, pane: &View, cx: &mut ViewContext, ) -> Option>; - fn follower_states_mut(&mut self) -> &mut HashMap, FollowerState>; - fn follower_states(&self) -> &HashMap, FollowerState>; fn room_id(&self, cx: &AppContext) -> Option; fn is_in_room(&self, cx: &mut ViewContext) -> bool { self.room_id(cx).is_some() @@ -448,6 +446,7 @@ pub struct Workspace { notifications: Vec<(TypeId, usize, Box)>, project: Model, call_handler: Box, + follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, @@ -458,7 +457,6 @@ pub struct Workspace { _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, - call_factory: CallFactory, } impl EventEmitter for Workspace {} @@ -484,7 +482,6 @@ impl Workspace { workspace_id: WorkspaceId, project: Model, app_state: Arc, - call_factory: CallFactory, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |_, _, cx| cx.notify()).detach(); @@ -656,6 +653,7 @@ impl Workspace { ]; cx.defer(|this, cx| this.update_window_title(cx)); + let call_factory = cx.global::(); Workspace { window_self: window_handle, weak_self: weak_handle.clone(), @@ -675,7 +673,7 @@ impl Workspace { bottom_dock, right_dock, project: project.clone(), - + follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, @@ -689,7 +687,6 @@ impl Workspace { subscriptions, pane_history_timestamp, workspace_actions: Default::default(), - call_factory, } } @@ -697,7 +694,6 @@ impl Workspace { abs_paths: Vec, app_state: Arc, requesting_window: Option>, - call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -748,13 +744,7 @@ impl Workspace { let window = if let Some(window) = requesting_window { cx.update_window(window.into(), |old_workspace, cx| { cx.replace_root_view(|cx| { - Workspace::new( - workspace_id, - project_handle.clone(), - app_state.clone(), - call_factory, - cx, - ) + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); })?; window @@ -800,13 +790,7 @@ impl Workspace { let project_handle = project_handle.clone(); move |cx| { cx.build_view(|cx| { - Workspace::new( - workspace_id, - project_handle, - app_state, - call_factory, - cx, - ) + Workspace::new(workspace_id, project_handle, app_state, cx) }) } })? @@ -1067,13 +1051,7 @@ impl Workspace { if self.project.read(cx).is_local() { Task::Ready(Some(Ok(callback(self, cx)))) } else { - let task = Self::new_local( - Vec::new(), - self.app_state.clone(), - None, - self.call_factory, - cx, - ); + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); cx.spawn(|_vh, mut cx| async move { let (workspace, _) = task.await?; workspace.update(&mut cx, callback) @@ -1302,7 +1280,7 @@ impl Workspace { Some(self.prepare_to_close(false, cx)) }; let app_state = self.app_state.clone(); - let call_factory = self.call_factory; + cx.spawn(|_, mut cx| async move { let window_to_replace = if let Some(close_task) = close_task { if !close_task.await? { @@ -1312,7 +1290,7 @@ impl Workspace { } else { None }; - cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, call_factory, cx))? + cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))? .await?; Ok(()) }) @@ -2282,7 +2260,7 @@ impl Workspace { } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - self.call_handler.follower_states_mut().retain(|_, state| { + self.follower_states.retain(|_, state| { if state.leader_id == peer_id { for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); @@ -2435,7 +2413,7 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let follower_states = self.call_handler.follower_states_mut(); + let follower_states = &mut self.follower_states; let state = follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { @@ -2658,7 +2636,7 @@ impl Workspace { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { this.update(cx, |this, _| { - for (_, state) in this.call_handler.follower_states_mut() { + for (_, state) in &mut this.follower_states { if state.leader_id == leader_id { state.active_view_id = if let Some(active_view_id) = update_active_view.id.clone() { @@ -2681,7 +2659,7 @@ impl Workspace { let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); - for (_, state) in this.call_handler.follower_states_mut() { + for (_, state) in &mut this.follower_states { if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { @@ -2695,8 +2673,7 @@ impl Workspace { } proto::update_followers::Variant::CreateView(view) => { let panes = this.update(cx, |this, _| { - this.call_handler - .follower_states() + this.follower_states .iter() .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() @@ -2756,7 +2733,7 @@ impl Workspace { for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { let items = futures::future::try_join_all(item_tasks).await?; this.update(cx, |this, cx| { - let state = this.call_handler.follower_states_mut().get_mut(&pane)?; + let state = this.follower_states.get_mut(&pane)?; for (id, item) in leader_view_ids.into_iter().zip(items) { item.set_leader_peer_id(Some(leader_id), cx); state.items_by_leader_view_id.insert(id, item); @@ -2813,14 +2790,55 @@ impl Workspace { } pub fn leader_for_pane(&self, pane: &View) -> Option { - self.call_handler - .follower_states() - .get(pane) - .map(|state| state.leader_id) + self.follower_states.get(pane).map(|state| state.leader_id) } - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { - self.call_handler.leader_updated(leader_id, cx) + pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + cx.notify(); + + let (leader_in_this_project, leader_in_this_app) = + self.call_handler.peer_state(leader_id, cx)?; + let mut items_to_activate = Vec::new(); + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { + if leader_in_this_project || !item.is_project_item(cx) { + items_to_activate.push((pane.clone(), item.boxed_clone())); + } + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); + } + continue; + } + // todo!() + // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + // items_to_activate.push((pane.clone(), Box::new(shared_screen))); + // } + } + + for (pane, item) in items_to_activate { + let pane_was_focused = pane.read(cx).has_focus(cx); + if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { + pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); + } else { + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + }); + } + + if pane_was_focused { + pane.update(cx, |pane, cx| pane.focus_active_item(cx)); + } + } + + None } // todo!() @@ -3637,7 +3655,7 @@ impl Render for Workspace { .flex_1() .child(self.center.render( &self.project, - &self.call_handler.follower_states(), + &self.follower_states, &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -4201,7 +4219,6 @@ pub fn open_paths( abs_paths: &[PathBuf], app_state: &Arc, requesting_window: Option>, - call_factory: CallFactory, cx: &mut AppContext, ) -> Task< anyhow::Result<( @@ -4228,13 +4245,7 @@ pub fn open_paths( todo!() } else { cx.update(move |cx| { - Workspace::new_local( - abs_paths, - app_state.clone(), - requesting_window, - call_factory, - cx, - ) + Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) })? .await } @@ -4245,9 +4256,8 @@ pub fn open_new( app_state: &Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, - call_factory: CallFactory, ) -> Task<()> { - let task = Workspace::new_local(Vec::new(), app_state.clone(), None, call_factory, cx); + let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx); cx.spawn(|mut cx| async move { if let Some((workspace, opened_paths)) = task.await.log_err() { workspace diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 9c42badb85..62d337a716 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -189,7 +189,7 @@ fn main() { // audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - workspace::init(app_state.clone(), cx); + workspace::init(app_state.clone(), cx, call::Call::new); // recent_projects::init(cx); go_to_line::init(cx); From 7e7a778d116938d9ef2ff219f7a4e73c952e00ae Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:04:02 +0100 Subject: [PATCH 06/27] Move CallFactory into AppState Fix crash caused by double borrow of window handle --- crates/workspace2/src/workspace2.rs | 17 ++++++----------- crates/zed2/src/main.rs | 3 ++- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 754988a605..e1e79c4d3e 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -207,10 +207,9 @@ pub fn init_settings(cx: &mut AppContext) { ItemSettings::register(cx); } -pub fn init(app_state: Arc, cx: &mut AppContext, call_factory: CallFactory) { +pub fn init(app_state: Arc, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); - cx.set_global(call_factory); // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { @@ -304,6 +303,7 @@ pub struct AppState { pub user_store: Model, pub workspace_store: Model, pub fs: Arc, + pub call_factory: CallFactory, pub build_window_options: fn(Option, Option, &mut AppContext) -> WindowOptions, pub node_runtime: Arc, @@ -653,7 +653,6 @@ impl Workspace { ]; cx.defer(|this, cx| this.update_window_title(cx)); - let call_factory = cx.global::(); Workspace { window_self: window_handle, weak_self: weak_handle.clone(), @@ -677,7 +676,7 @@ impl Workspace { last_leaders_by_pane: Default::default(), window_edited: false, - call_handler: call_factory(weak_handle.clone(), cx), + call_handler: (app_state.call_factory)(weak_handle.clone(), cx), database_id: workspace_id, app_state, _observe_current_user, @@ -2784,8 +2783,9 @@ impl Workspace { } else { None }; + let room_id = self.call_handler.room_id(cx)?; self.app_state().workspace_store.update(cx, |store, cx| { - store.update_followers(project_id, update, cx) + store.update_followers(project_id, room_id, update, cx) }) } @@ -3825,15 +3825,10 @@ impl WorkspaceStore { pub fn update_followers( &self, project_id: Option, + room_id: u64, update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { - let room_id = self.workspaces.iter().next().and_then(|workspace| { - workspace - .read_with(cx, |this, cx| this.call_handler.room_id(cx)) - .log_err() - .flatten() - })?; let follower_ids: Vec<_> = self .followers .iter() diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 62d337a716..b0a03d8684 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -180,6 +180,7 @@ fn main() { user_store, fs, build_window_options, + call_factory: call::Call::new, // background_actions: todo!("ask Mikayla"), workspace_store, node_runtime, @@ -189,7 +190,7 @@ fn main() { // audio::init(Assets, cx); auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); - workspace::init(app_state.clone(), cx, call::Call::new); + workspace::init(app_state.clone(), cx); // recent_projects::init(cx); go_to_line::init(cx); From c199d92daca3eb5a46ced2dc8fd125de771f881c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 21 Nov 2023 18:14:14 -0800 Subject: [PATCH 07/27] Update main.rs --- crates/zed2/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 9c42badb85..c6737628a9 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -355,7 +355,6 @@ async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncApp cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))? .await .log_err(); - } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) { // todo!(welcome) //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { //todo!() From 3a8e9b569743a1c05584b5416fe2b68e1cd52ee5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 11:40:38 +0100 Subject: [PATCH 08/27] Avoid holding borrow to editor while painting child elements --- crates/editor2/src/element.rs | 1313 ++++++++++++++--------------- crates/gpui2/src/elements/text.rs | 5 +- 2 files changed, 648 insertions(+), 670 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index add9c9ad33..7f6135087a 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -124,6 +124,180 @@ impl EditorElement { } } + fn register_actions(&self, cx: &mut WindowContext) { + let view = &self.editor; + register_action(view, cx, Editor::move_left); + 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); + register_action(view, cx, Editor::newline_below); + register_action(view, cx, Editor::backspace); + register_action(view, cx, Editor::delete); + register_action(view, cx, Editor::tab); + register_action(view, cx, Editor::tab_prev); + register_action(view, cx, Editor::indent); + register_action(view, cx, Editor::outdent); + register_action(view, cx, Editor::delete_line); + register_action(view, cx, Editor::join_lines); + register_action(view, cx, Editor::sort_lines_case_sensitive); + register_action(view, cx, Editor::sort_lines_case_insensitive); + register_action(view, cx, Editor::reverse_lines); + register_action(view, cx, Editor::shuffle_lines); + register_action(view, cx, Editor::convert_to_upper_case); + register_action(view, cx, Editor::convert_to_lower_case); + register_action(view, cx, Editor::convert_to_title_case); + register_action(view, cx, Editor::convert_to_snake_case); + register_action(view, cx, Editor::convert_to_kebab_case); + register_action(view, cx, Editor::convert_to_upper_camel_case); + register_action(view, cx, Editor::convert_to_lower_camel_case); + register_action(view, cx, Editor::delete_to_previous_word_start); + register_action(view, cx, Editor::delete_to_previous_subword_start); + register_action(view, cx, Editor::delete_to_next_word_end); + register_action(view, cx, Editor::delete_to_next_subword_end); + register_action(view, cx, Editor::delete_to_beginning_of_line); + register_action(view, cx, Editor::delete_to_end_of_line); + register_action(view, cx, Editor::cut_to_end_of_line); + register_action(view, cx, Editor::duplicate_line); + register_action(view, cx, Editor::move_line_up); + register_action(view, cx, Editor::move_line_down); + register_action(view, cx, Editor::transpose); + register_action(view, cx, Editor::cut); + register_action(view, cx, Editor::copy); + register_action(view, cx, Editor::paste); + register_action(view, cx, Editor::undo); + register_action(view, cx, Editor::redo); + register_action(view, cx, Editor::move_page_up); + register_action(view, cx, Editor::move_page_down); + register_action(view, cx, Editor::next_screen); + register_action(view, cx, Editor::scroll_cursor_top); + register_action(view, cx, Editor::scroll_cursor_center); + register_action(view, cx, Editor::scroll_cursor_bottom); + register_action(view, cx, |editor, _: &LineDown, cx| { + editor.scroll_screen(&ScrollAmount::Line(1.), cx) + }); + register_action(view, cx, |editor, _: &LineUp, cx| { + editor.scroll_screen(&ScrollAmount::Line(-1.), cx) + }); + register_action(view, cx, |editor, _: &HalfPageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.5), cx) + }); + register_action(view, cx, |editor, _: &HalfPageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) + }); + register_action(view, cx, |editor, _: &PageDown, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.), cx) + }); + register_action(view, cx, |editor, _: &PageUp, cx| { + editor.scroll_screen(&ScrollAmount::Page(-1.), cx) + }); + register_action(view, cx, Editor::move_to_previous_word_start); + register_action(view, cx, Editor::move_to_previous_subword_start); + register_action(view, cx, Editor::move_to_next_word_end); + register_action(view, cx, Editor::move_to_next_subword_end); + register_action(view, cx, Editor::move_to_beginning_of_line); + register_action(view, cx, Editor::move_to_end_of_line); + register_action(view, cx, Editor::move_to_start_of_paragraph); + register_action(view, cx, Editor::move_to_end_of_paragraph); + register_action(view, cx, Editor::move_to_beginning); + register_action(view, cx, Editor::move_to_end); + register_action(view, cx, Editor::select_up); + register_action(view, cx, Editor::select_down); + register_action(view, cx, Editor::select_left); + register_action(view, cx, Editor::select_right); + register_action(view, cx, Editor::select_to_previous_word_start); + register_action(view, cx, Editor::select_to_previous_subword_start); + register_action(view, cx, Editor::select_to_next_word_end); + register_action(view, cx, Editor::select_to_next_subword_end); + register_action(view, cx, Editor::select_to_beginning_of_line); + register_action(view, cx, Editor::select_to_end_of_line); + register_action(view, cx, Editor::select_to_start_of_paragraph); + register_action(view, cx, Editor::select_to_end_of_paragraph); + register_action(view, cx, Editor::select_to_beginning); + register_action(view, cx, Editor::select_to_end); + register_action(view, cx, Editor::select_all); + register_action(view, cx, |editor, action, cx| { + editor.select_all_matches(action, cx).log_err(); + }); + register_action(view, cx, Editor::select_line); + register_action(view, cx, Editor::split_selection_into_lines); + register_action(view, cx, Editor::add_selection_above); + register_action(view, cx, Editor::add_selection_below); + register_action(view, cx, |editor, action, cx| { + editor.select_next(action, cx).log_err(); + }); + register_action(view, cx, |editor, action, cx| { + editor.select_previous(action, cx).log_err(); + }); + register_action(view, cx, Editor::toggle_comments); + register_action(view, cx, Editor::select_larger_syntax_node); + register_action(view, cx, Editor::select_smaller_syntax_node); + register_action(view, cx, Editor::move_to_enclosing_bracket); + register_action(view, cx, Editor::undo_selection); + register_action(view, cx, Editor::redo_selection); + register_action(view, cx, Editor::go_to_diagnostic); + register_action(view, cx, Editor::go_to_prev_diagnostic); + register_action(view, cx, Editor::go_to_hunk); + register_action(view, cx, Editor::go_to_prev_hunk); + register_action(view, cx, Editor::go_to_definition); + register_action(view, cx, Editor::go_to_definition_split); + register_action(view, cx, Editor::go_to_type_definition); + register_action(view, cx, Editor::go_to_type_definition_split); + register_action(view, cx, Editor::fold); + register_action(view, cx, Editor::fold_at); + register_action(view, cx, Editor::unfold_lines); + register_action(view, cx, Editor::unfold_at); + 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::toggle_soft_wrap); + register_action(view, cx, Editor::toggle_inlay_hints); + register_action(view, cx, Editor::reveal_in_finder); + register_action(view, cx, Editor::copy_path); + register_action(view, cx, Editor::copy_relative_path); + register_action(view, cx, Editor::copy_highlight_json); + register_action(view, cx, |editor, action, cx| { + editor + .format(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + register_action(view, cx, Editor::restart_language_server); + register_action(view, cx, Editor::show_character_palette); + // on_action(cx, Editor::confirm_completion); todo!() + register_action(view, cx, |editor, action, cx| { + editor + .confirm_code_action(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + register_action(view, cx, |editor, action, cx| { + editor + .rename(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + register_action(view, cx, |editor, action, cx| { + editor + .confirm_rename(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + register_action(view, cx, |editor, action, cx| { + editor + .find_all_references(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }); + register_action(view, cx, Editor::next_copilot_suggestion); + register_action(view, cx, Editor::previous_copilot_suggestion); + register_action(view, cx, Editor::copilot_suggest); + register_action(view, cx, Editor::context_menu_first); + register_action(view, cx, Editor::context_menu_prev); + register_action(view, cx, Editor::context_menu_next); + register_action(view, cx, Editor::context_menu_last); + } + fn mouse_down( editor: &mut Editor, event: &MouseDownEvent, @@ -459,7 +633,6 @@ impl EditorElement { &mut self, bounds: Bounds, layout: &mut LayoutState, - editor: &mut Editor, cx: &mut WindowContext, ) { let line_height = layout.position_map.line_height; @@ -616,14 +789,19 @@ impl EditorElement { &mut self, text_bounds: Bounds, layout: &mut LayoutState, - editor: &mut Editor, cx: &mut WindowContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); let start_row = layout.visible_display_row_range.start; let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); let line_end_overshoot = 0.15 * layout.position_map.line_height; - let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; + let whitespace_setting = self + .editor + .read(cx) + .buffer + .read(cx) + .settings_at(0, cx) + .show_whitespaces; cx.with_content_mask( Some(ContentMask { @@ -748,7 +926,7 @@ impl EditorElement { invisible_display_ranges.push(selection.range.clone()); } - if !selection.is_local || editor.show_local_cursors(cx) { + if !selection.is_local || self.editor.read(cx).show_local_cursors(cx) { let cursor_position = selection.head; if layout .visible_display_row_range @@ -800,12 +978,14 @@ impl EditorElement { * layout.position_map.line_height - layout.position_map.scroll_position.y; if selection.is_newest { - editor.pixel_position_of_newest_cursor = Some(point( - text_bounds.origin.x + x + block_width / 2., - text_bounds.origin.y - + y - + layout.position_map.line_height / 2., - )); + self.editor.update(cx, |editor, _| { + editor.pixel_position_of_newest_cursor = Some(point( + text_bounds.origin.x + x + block_width / 2., + text_bounds.origin.y + + y + + layout.position_map.line_height / 2., + )) + }); } cursors.push(Cursor { color: selection_style.cursor, @@ -1217,7 +1397,6 @@ impl EditorElement { &mut self, bounds: Bounds, layout: &mut LayoutState, - editor: &mut Editor, cx: &mut WindowContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -1237,7 +1416,7 @@ impl EditorElement { } } - fn column_pixels(&self, column: usize, cx: &ViewContext) -> Pixels { + fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels { let style = &self.style; let font_size = style.text.font_size.to_pixels(cx.rem_size()); let layout = cx @@ -1258,7 +1437,7 @@ impl EditorElement { layout.width } - fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> Pixels { + fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels { let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; self.column_pixels(digit_count, cx) } @@ -1413,7 +1592,7 @@ impl EditorElement { } fn layout_lines( - &mut self, + &self, rows: Range, line_number_layouts: &[Option], snapshot: &EditorSnapshot, @@ -1469,483 +1648,469 @@ impl EditorElement { fn compute_layout( &mut self, - editor: &mut Editor, - cx: &mut ViewContext<'_, Editor>, mut bounds: Bounds, + cx: &mut WindowContext, ) -> LayoutState { - // let mut size = constraint.max; - // if size.x.is_infinite() { - // unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); - // } + 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(); + let snapshot = editor.snapshot(cx); + let style = self.style.clone(); - let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); - let font_size = style.text.font_size.to_pixels(cx.rem_size()); - let line_height = style.text.line_height_in_pixels(cx.rem_size()); - let em_width = cx - .text_system() - .typographic_bounds(font_id, font_size, 'm') - .unwrap() - .size - .width; - let em_advance = cx - .text_system() - .advance(font_id, font_size, 'm') - .unwrap() - .width; + let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_size = style.text.font_size.to_pixels(cx.rem_size()); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); + let em_width = cx + .text_system() + .typographic_bounds(font_id, font_size, 'm') + .unwrap() + .size + .width; + let em_advance = cx + .text_system() + .advance(font_id, font_size, 'm') + .unwrap() + .width; - let gutter_padding; - let gutter_width; - let gutter_margin; - if snapshot.show_gutter { - let descent = cx.text_system().descent(font_id, font_size).unwrap(); + let gutter_padding; + let gutter_width; + let gutter_margin; + if snapshot.show_gutter { + let descent = cx.text_system().descent(font_id, font_size).unwrap(); - let gutter_padding_factor = 3.5; - gutter_padding = (em_width * gutter_padding_factor).round(); - gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; - gutter_margin = -descent; - } else { - gutter_padding = Pixels::ZERO; - gutter_width = Pixels::ZERO; - gutter_margin = Pixels::ZERO; - }; - - editor.gutter_width = gutter_width; - let text_width = bounds.size.width - gutter_width; - let overscroll = size(em_width, px(0.)); - let snapshot = { - editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); - - let editor_width = text_width - gutter_margin - overscroll.width - 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), + let gutter_padding_factor = 3.5; + gutter_padding = (em_width * gutter_padding_factor).round(); + gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; + gutter_margin = -descent; + } else { + gutter_padding = Pixels::ZERO; + gutter_width = Pixels::ZERO; + gutter_margin = Pixels::ZERO; }; - if editor.set_wrap_width(Some(wrap_width), cx) { - editor.snapshot(cx) + editor.gutter_width = gutter_width; + let text_width = bounds.size.width - gutter_width; + let overscroll = size(em_width, px(0.)); + let snapshot = { + editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); + + let editor_width = text_width - gutter_margin - overscroll.width - 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 = 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); + + let autoscroll_horizontally = + editor.autoscroll_vertically(bounds.size.height, 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 = f32::from(bounds.size.height / 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 wrap_guides = editor - .wrap_guides(cx) - .iter() - .map(|(guide, active)| (self.column_pixels(*guide, cx), *active)) - .collect::>(); + let mut selections: Vec<(PlayerColor, Vec)> = Vec::new(); + let mut active_rows = BTreeMap::new(); + let is_singleton = editor.is_singleton(cx); - 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); + let highlighted_rows = editor.highlighted_rows(); + let highlighted_ranges = editor.background_highlights_in_range( + start_anchor..end_anchor, + &snapshot.display_snapshot, + cx.theme().colors(), + ); - let autoscroll_horizontally = - editor.autoscroll_vertically(bounds.size.height, line_height, cx); - let mut snapshot = editor.snapshot(cx); + let mut newest_selection_head = None; - 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 = f32::from(bounds.size.height / line_height); - let max_row = snapshot.max_point().row(); + if editor.show_local_selections { + let mut local_selections: Vec> = 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; - // 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 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); + } - 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<(PlayerColor, Vec)> = Vec::new(); - let mut active_rows = BTreeMap::new(); - let is_singleton = editor.is_singleton(cx); - - let highlighted_rows = editor.highlighted_rows(); - let highlighted_ranges = editor.background_highlights_in_range( - start_anchor..end_anchor, - &snapshot.display_snapshot, - cx.theme().colors(), - ); - - let mut newest_selection_head = None; - - if editor.show_local_selections { - let mut local_selections: Vec> = 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.local_player, 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) + for row in cmp::max(layout.active_rows.start, start_row) + ..=cmp::min(layout.active_rows.end, end_row) { - if let Some((local_selection_style, _)) = selections.first_mut() { - *local_selection_style = cx - .theme() - .players() - .color_for_participant(participant_index.0); + 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.local_player, 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 = cx + .theme() + .players() + .color_for_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 { - cx.theme() - .players() - .color_for_participant(participant_index.0) - } else { - cx.theme().players().absent() - }; + 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 { + cx.theme() + .players() + .color_for_participant(participant_index.0) + } else { + cx.theme().players().absent() + }; - // Don't re-render the leader's selections, since the local selections - // match theirs. - if Some(selection.peer_id) == editor.leader_peer_id { - continue; + // 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, + )); } - 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()); } - selections.extend(remote_selections.into_values()); - } + let scrollbar_settings = EditorSettings::get_global(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 scrollbar_settings = EditorSettings::get_global(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 head_for_relative = newest_selection_head.unwrap_or_else(|| { + let newest = editor.selections.newest::(cx); + SelectionLayout::new( + newest, + editor.selections.line_mode, + editor.cursor_shape, + &snapshot.display_snapshot, + true, + true, + ) + .head + }); - let head_for_relative = newest_selection_head.unwrap_or_else(|| { - let newest = editor.selections.newest::(cx); - SelectionLayout::new( - newest, - editor.selections.line_mode, - editor.cursor_shape, - &snapshot.display_snapshot, - true, - true, - ) - .head - }); - - let (line_numbers, fold_statuses) = self.shape_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 = Pixels::ZERO; - let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &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 longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) - .unwrap() - .width; - let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - - let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { - self.layout_blocks( + let (line_numbers, fold_statuses) = self.shape_line_numbers( start_row..end_row, + &active_rows, + head_for_relative, + is_singleton, &snapshot, - bounds.size.width, - scroll_width, - gutter_padding, - gutter_width, - em_width, - gutter_width + gutter_margin, - line_height, - &style, - &line_layouts, - editor, cx, - ) - }); + ); - let scroll_max = point( - f32::from((scroll_width - text_size.width) / em_width).max(0.0), - max_row as f32, - ); + let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); - let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x); + let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines); - let autoscrolled = if autoscroll_horizontally { - editor.autoscroll_horizontally( - start_row, - text_size.width, - scroll_width, - em_width, - &line_layouts, - cx, - ) - } else { - false - }; - - if clamped || autoscrolled { - snapshot = editor.snapshot(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, &self.style, cx); + let mut max_visible_line_width = Pixels::ZERO; + let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &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 active = matches!( - editor.context_menu.read().as_ref(), - Some(crate::ContextMenu::CodeActions(_)) - ); - - code_actions_indicator = editor - .render_code_actions_indicator(&style, active, cx) - .map(|element| CodeActionsIndicator { - row: newest_selection_head.row(), - button: element, - }); } - } - let visible_rows = start_row..start_row + line_layouts.len() as u32; - // todo!("hover") - // 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 longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx) + .unwrap() + .width; + let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - let mut fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { - editor.render_fold_indicators( - fold_statuses, - &style, - editor.gutter_hovered, - line_height, + let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { + self.layout_blocks( + start_row..end_row, + &snapshot, + bounds.size.width, + scroll_width, + gutter_padding, + gutter_width, + em_width, + gutter_width + gutter_margin, + line_height, + &style, + &line_layouts, + editor, + cx, + ) + }); + + let scroll_max = point( + f32::from((scroll_width - text_size.width) / 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.width, + scroll_width, + em_width, + &line_layouts, + cx, + ) + } else { + false + }; + + if clamped || autoscrolled { + snapshot = editor.snapshot(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, &self.style, 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(|element| CodeActionsIndicator { + row: newest_selection_head.row(), + button: element, + }); + } + } + + let visible_rows = start_row..start_row + line_layouts.len() as u32; + // todo!("hover") + // 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 = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { + editor.render_fold_indicators( + fold_statuses, + &style, + editor.gutter_hovered, + line_height, + gutter_margin, + cx, + ) + }); + + // todo!("hover popovers") + // if let Some((_, hover_popovers)) = hover.as_mut() { + // for hover_popover in hover_popovers.iter_mut() { + // hover_popover.layout( + // SizeConstraint { + // min: gpui::Point::::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 = font_size / 2.; + let tab_invisible = cx + .text_system() + .shape_line( + "→".into(), + invisible_symbol_font_size, + &[TextRun { + len: "→".len(), + font: self.style.text.font(), + color: cx.theme().colors().editor_invisible, + background_color: None, + underline: None, + }], + ) + .unwrap(); + let space_invisible = cx + .text_system() + .shape_line( + "•".into(), + invisible_symbol_font_size, + &[TextRun { + len: "•".len(), + font: self.style.text.font(), + color: cx.theme().colors().editor_invisible, + background_color: None, + underline: None, + }], + ) + .unwrap(); + + LayoutState { + mode: editor_mode, + position_map: Arc::new(PositionMap { + size: bounds.size, + scroll_position: point( + scroll_position.x * em_width, + scroll_position.y * line_height, + ), + scroll_max, + line_layouts, + line_height, + em_width, + em_advance, + snapshot, + }), + visible_anchor_range: start_anchor..end_anchor, + 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, - cx, - ) - }); - - // todo!("context_menu") - // if let Some((_, context_menu)) = context_menu.as_mut() { - // context_menu.layout( - // SizeConstraint { - // min: gpui::Point::::zero(), - // max: point( - // cx.window_size().x * 0.7, - // (12. * line_height).min((size.y - line_height) / 2.), - // ), - // }, - // editor, - // cx, - // ); - // } - - // todo!("hover popovers") - // if let Some((_, hover_popovers)) = hover.as_mut() { - // for hover_popover in hover_popovers.iter_mut() { - // hover_popover.layout( - // SizeConstraint { - // min: gpui::Point::::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 = font_size / 2.; - let tab_invisible = cx - .text_system() - .shape_line( - "→".into(), - invisible_symbol_font_size, - &[TextRun { - len: "→".len(), - font: self.style.text.font(), - color: cx.theme().colors().editor_invisible, - background_color: None, - underline: None, - }], - ) - .unwrap(); - let space_invisible = cx - .text_system() - .shape_line( - "•".into(), - invisible_symbol_font_size, - &[TextRun { - len: "•".len(), - font: self.style.text.font(), - color: cx.theme().colors().editor_invisible, - background_color: None, - underline: None, - }], - ) - .unwrap(); - - LayoutState { - mode: editor_mode, - position_map: Arc::new(PositionMap { - size: bounds.size, - scroll_position: point( - scroll_position.x * em_width, - scroll_position.y * line_height, - ), - scroll_max, - line_layouts, - line_height, - em_width, - em_advance, - snapshot, - }), - visible_anchor_range: start_anchor..end_anchor, - 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, - line_numbers, - display_hunks, - blocks, - selections, - context_menu, - code_actions_indicator, - fold_indicators, - tab_invisible, - space_invisible, - // hover_popovers: hover, - } + active_rows, + highlighted_rows, + highlighted_ranges, + line_numbers, + display_hunks, + blocks, + selections, + context_menu, + code_actions_indicator, + fold_indicators, + tab_invisible, + space_invisible, + // hover_popovers: hover, + } + }) } #[allow(clippy::too_many_arguments)] fn layout_blocks( - &mut self, + &self, rows: Range, snapshot: &EditorSnapshot, editor_width: Pixels, @@ -2446,55 +2611,44 @@ impl Element for EditorElement { cx: &mut gpui::WindowContext, ) { let editor = self.editor.clone(); - editor.update(cx, |editor, cx| { - let mut layout = self.compute_layout(editor, cx, bounds); - let gutter_bounds = Bounds { - origin: bounds.origin, - size: layout.gutter_size, - }; - let text_bounds = Bounds { - origin: gutter_bounds.upper_right(), - size: layout.text_size, - }; - let dispatch_context = editor.dispatch_context(cx); - let editor_handle = cx.view().clone(); - cx.with_key_dispatch( - dispatch_context, - Some(editor.focus_handle.clone()), - |_, cx| { - register_actions(&editor_handle, cx); + let mut layout = self.compute_layout(bounds, cx); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners first, so any elements we paint on top of the editor - // take precedence. - self.paint_mouse_listeners( - bounds, - gutter_bounds, - text_bounds, - &layout, - cx, - ); - let input_handler = ElementInputHandler::new(bounds, editor_handle, cx); - cx.handle_input(&editor.focus_handle, input_handler); + let focus_handle = editor.focus_handle(cx); + 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.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, editor, cx); - } - self.paint_text(text_bounds, &mut layout, editor, cx); + // We call with_z_index to establish a new stacking context. + cx.with_z_index(0, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + // Paint mouse listeners first, so any elements we paint on top of the editor + // take precedence. + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); + let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - if !layout.blocks.is_empty() { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, editor, cx); - }) - } - }); - }); - }, - ) + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); + + if !layout.blocks.is_empty() { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }) + } + }); + }); }) } } @@ -3932,179 +4086,6 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 { // } // } -fn register_actions(view: &View, cx: &mut WindowContext) { - register_action(view, cx, Editor::move_left); - 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); - register_action(view, cx, Editor::newline_below); - register_action(view, cx, Editor::backspace); - register_action(view, cx, Editor::delete); - register_action(view, cx, Editor::tab); - register_action(view, cx, Editor::tab_prev); - register_action(view, cx, Editor::indent); - register_action(view, cx, Editor::outdent); - register_action(view, cx, Editor::delete_line); - register_action(view, cx, Editor::join_lines); - register_action(view, cx, Editor::sort_lines_case_sensitive); - register_action(view, cx, Editor::sort_lines_case_insensitive); - register_action(view, cx, Editor::reverse_lines); - register_action(view, cx, Editor::shuffle_lines); - register_action(view, cx, Editor::convert_to_upper_case); - register_action(view, cx, Editor::convert_to_lower_case); - register_action(view, cx, Editor::convert_to_title_case); - register_action(view, cx, Editor::convert_to_snake_case); - register_action(view, cx, Editor::convert_to_kebab_case); - register_action(view, cx, Editor::convert_to_upper_camel_case); - register_action(view, cx, Editor::convert_to_lower_camel_case); - register_action(view, cx, Editor::delete_to_previous_word_start); - register_action(view, cx, Editor::delete_to_previous_subword_start); - register_action(view, cx, Editor::delete_to_next_word_end); - register_action(view, cx, Editor::delete_to_next_subword_end); - register_action(view, cx, Editor::delete_to_beginning_of_line); - register_action(view, cx, Editor::delete_to_end_of_line); - register_action(view, cx, Editor::cut_to_end_of_line); - register_action(view, cx, Editor::duplicate_line); - register_action(view, cx, Editor::move_line_up); - register_action(view, cx, Editor::move_line_down); - register_action(view, cx, Editor::transpose); - register_action(view, cx, Editor::cut); - register_action(view, cx, Editor::copy); - register_action(view, cx, Editor::paste); - register_action(view, cx, Editor::undo); - register_action(view, cx, Editor::redo); - register_action(view, cx, Editor::move_page_up); - register_action(view, cx, Editor::move_page_down); - register_action(view, cx, Editor::next_screen); - register_action(view, cx, Editor::scroll_cursor_top); - register_action(view, cx, Editor::scroll_cursor_center); - register_action(view, cx, Editor::scroll_cursor_bottom); - register_action(view, cx, |editor, _: &LineDown, cx| { - editor.scroll_screen(&ScrollAmount::Line(1.), cx) - }); - register_action(view, cx, |editor, _: &LineUp, cx| { - editor.scroll_screen(&ScrollAmount::Line(-1.), cx) - }); - register_action(view, cx, |editor, _: &HalfPageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(0.5), cx) - }); - register_action(view, cx, |editor, _: &HalfPageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-0.5), cx) - }); - register_action(view, cx, |editor, _: &PageDown, cx| { - editor.scroll_screen(&ScrollAmount::Page(1.), cx) - }); - register_action(view, cx, |editor, _: &PageUp, cx| { - editor.scroll_screen(&ScrollAmount::Page(-1.), cx) - }); - register_action(view, cx, Editor::move_to_previous_word_start); - register_action(view, cx, Editor::move_to_previous_subword_start); - register_action(view, cx, Editor::move_to_next_word_end); - register_action(view, cx, Editor::move_to_next_subword_end); - register_action(view, cx, Editor::move_to_beginning_of_line); - register_action(view, cx, Editor::move_to_end_of_line); - register_action(view, cx, Editor::move_to_start_of_paragraph); - register_action(view, cx, Editor::move_to_end_of_paragraph); - register_action(view, cx, Editor::move_to_beginning); - register_action(view, cx, Editor::move_to_end); - register_action(view, cx, Editor::select_up); - register_action(view, cx, Editor::select_down); - register_action(view, cx, Editor::select_left); - register_action(view, cx, Editor::select_right); - register_action(view, cx, Editor::select_to_previous_word_start); - register_action(view, cx, Editor::select_to_previous_subword_start); - register_action(view, cx, Editor::select_to_next_word_end); - register_action(view, cx, Editor::select_to_next_subword_end); - register_action(view, cx, Editor::select_to_beginning_of_line); - register_action(view, cx, Editor::select_to_end_of_line); - register_action(view, cx, Editor::select_to_start_of_paragraph); - register_action(view, cx, Editor::select_to_end_of_paragraph); - register_action(view, cx, Editor::select_to_beginning); - register_action(view, cx, Editor::select_to_end); - register_action(view, cx, Editor::select_all); - register_action(view, cx, |editor, action, cx| { - editor.select_all_matches(action, cx).log_err(); - }); - register_action(view, cx, Editor::select_line); - register_action(view, cx, Editor::split_selection_into_lines); - register_action(view, cx, Editor::add_selection_above); - register_action(view, cx, Editor::add_selection_below); - register_action(view, cx, |editor, action, cx| { - editor.select_next(action, cx).log_err(); - }); - register_action(view, cx, |editor, action, cx| { - editor.select_previous(action, cx).log_err(); - }); - register_action(view, cx, Editor::toggle_comments); - register_action(view, cx, Editor::select_larger_syntax_node); - register_action(view, cx, Editor::select_smaller_syntax_node); - register_action(view, cx, Editor::move_to_enclosing_bracket); - register_action(view, cx, Editor::undo_selection); - register_action(view, cx, Editor::redo_selection); - register_action(view, cx, Editor::go_to_diagnostic); - register_action(view, cx, Editor::go_to_prev_diagnostic); - register_action(view, cx, Editor::go_to_hunk); - register_action(view, cx, Editor::go_to_prev_hunk); - register_action(view, cx, Editor::go_to_definition); - register_action(view, cx, Editor::go_to_definition_split); - register_action(view, cx, Editor::go_to_type_definition); - register_action(view, cx, Editor::go_to_type_definition_split); - register_action(view, cx, Editor::fold); - register_action(view, cx, Editor::fold_at); - register_action(view, cx, Editor::unfold_lines); - register_action(view, cx, Editor::unfold_at); - 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::toggle_soft_wrap); - register_action(view, cx, Editor::toggle_inlay_hints); - register_action(view, cx, Editor::reveal_in_finder); - register_action(view, cx, Editor::copy_path); - register_action(view, cx, Editor::copy_relative_path); - register_action(view, cx, Editor::copy_highlight_json); - register_action(view, cx, |editor, action, cx| { - editor - .format(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }); - register_action(view, cx, Editor::restart_language_server); - register_action(view, cx, Editor::show_character_palette); - // on_action(cx, Editor::confirm_completion); todo!() - register_action(view, cx, |editor, action, cx| { - editor - .confirm_code_action(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }); - register_action(view, cx, |editor, action, cx| { - editor - .rename(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }); - register_action(view, cx, |editor, action, cx| { - editor - .confirm_rename(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }); - register_action(view, cx, |editor, action, cx| { - editor - .find_all_references(action, cx) - .map(|task| task.detach_and_log_err(cx)); - }); - register_action(view, cx, Editor::next_copilot_suggestion); - register_action(view, cx, Editor::previous_copilot_suggestion); - register_action(view, cx, Editor::copilot_suggest); - register_action(view, cx, Editor::context_menu_first); - register_action(view, cx, Editor::context_menu_prev); - register_action(view, cx, Editor::context_menu_next); - register_action(view, cx, Editor::context_menu_last); -} - fn register_action( view: &View, cx: &mut WindowContext, diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 05ab85ca63..8c5f7d8e1b 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -174,10 +174,7 @@ impl TextState { let Some(lines) = text_system .shape_text( - &text, - font_size, - &runs[..], - wrap_width, // Wrap if we know the width. + &text, font_size, &runs, wrap_width, // Wrap if we know the width. ) .log_err() else { From fff2d7955efad77856db6c313046f12e257b5dc3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 14:01:22 +0100 Subject: [PATCH 09/27] Round up line width --- crates/gpui2/src/elements/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 8c5f7d8e1b..a35fc89997 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -191,7 +191,7 @@ impl TextState { for line in &lines { let line_size = line.size(line_height); size.height += line_size.height; - size.width = size.width.max(line_size.width); + size.width = size.width.max(line_size.width).ceil(); } element_state.lock().replace(TextStateInner { From 2b6e8de11f789fe6f3ee1d94acc26b5aa10bb7bf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 14:23:09 +0100 Subject: [PATCH 10/27] Don't perform wrapping in completions --- crates/editor2/src/editor.rs | 8 ++++++-- crates/gpui2/src/elements/text.rs | 14 +++++++++----- crates/gpui2/src/style.rs | 9 +++++++++ crates/gpui2/src/styled.rs | 20 +++++++++++++++++++- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 3801b965c2..690f6c2278 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -44,7 +44,7 @@ use gpui::{ EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, - ViewContext, VisualContext, WeakView, WindowContext, + ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -1358,9 +1358,11 @@ impl CompletionsMenu { // div() .id(mat.candidate_id) + .whitespace_nowrap() + .overflow_hidden() .bg(gpui::green()) .hover(|style| style.bg(gpui::blue())) - .when(item_ix == selected_item, |div| div.bg(gpui::blue())) + .when(item_ix == selected_item, |div| div.bg(gpui::red())) .child(SharedString::from(completion.label.text.clone())) .min_w(px(300.)) .max_w(px(700.)) @@ -9396,6 +9398,7 @@ impl Render for Editor { font_style: FontStyle::Normal, line_height: relative(1.).into(), underline: None, + white_space: WhiteSpace::Normal, }, EditorMode::AutoHeight { max_lines } => todo!(), @@ -9409,6 +9412,7 @@ impl Render for Editor { font_style: FontStyle::Normal, line_height: relative(settings.buffer_line_height.value()), underline: None, + white_space: WhiteSpace::Normal, }, }; diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index a35fc89997..a34de482d5 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,6 +1,6 @@ use crate::{ Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun, - WindowContext, WrappedLine, + WhiteSpace, WindowContext, WrappedLine, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -159,10 +159,14 @@ impl TextState { let element_state = self.clone(); move |known_dimensions, available_space| { - let wrap_width = known_dimensions.width.or(match available_space.width { - crate::AvailableSpace::Definite(x) => Some(x), - _ => None, - }); + let wrap_width = if text_style.white_space == WhiteSpace::Normal { + known_dimensions.width.or(match available_space.width { + crate::AvailableSpace::Definite(x) => Some(x), + _ => None, + }) + } else { + None + }; if let Some(text_state) = element_state.0.lock().as_ref() { if text_state.size.is_some() diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index f958b8b44c..c6f02f5bca 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -128,6 +128,13 @@ pub struct BoxShadow { pub spread_radius: Pixels, } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum WhiteSpace { + #[default] + Normal, + Nowrap, +} + #[derive(Refineable, Clone, Debug)] #[refineable(Debug)] pub struct TextStyle { @@ -139,6 +146,7 @@ pub struct TextStyle { pub font_weight: FontWeight, pub font_style: FontStyle, pub underline: Option, + pub white_space: WhiteSpace, } impl Default for TextStyle { @@ -152,6 +160,7 @@ impl Default for TextStyle { font_weight: FontWeight::default(), font_style: FontStyle::default(), underline: None, + white_space: WhiteSpace::Normal, } } } diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index beaf664dd8..bdb9d4b4fe 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -1,7 +1,7 @@ use crate::{ self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, - SharedString, StyleRefinement, Visibility, + SharedString, StyleRefinement, Visibility, WhiteSpace, }; use crate::{BoxShadow, TextStyleRefinement}; use smallvec::{smallvec, SmallVec}; @@ -101,6 +101,24 @@ pub trait Styled: Sized { self } + /// Sets the whitespace of the element to `normal`. + /// [Docs](https://tailwindcss.com/docs/whitespace#normal) + fn whitespace_normal(mut self) -> Self { + self.text_style() + .get_or_insert_with(Default::default) + .white_space = Some(WhiteSpace::Normal); + self + } + + /// Sets the whitespace of the element to `nowrap`. + /// [Docs](https://tailwindcss.com/docs/whitespace#nowrap) + fn whitespace_nowrap(mut self) -> Self { + self.text_style() + .get_or_insert_with(Default::default) + .white_space = Some(WhiteSpace::Nowrap); + self + } + /// Sets the flex direction of the element to `column`. /// [Docs](https://tailwindcss.com/docs/flex-direction#column) fn flex_col(mut self) -> Self { From 552f03c49d28ec73695afc6a8ae2d1fe7d5c2235 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:36:10 +0100 Subject: [PATCH 11/27] chore/CI: place .cargo/config.toml augmentations in ~/.cargo/config.toml --- .github/actions/run_tests/action.yml | 4 ---- .github/workflows/ci.yml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index de5eadb61a..8c1e09ee74 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -22,13 +22,9 @@ runs: run: script/clear-target-dir-if-larger-than 70 - name: Run check - env: - RUSTFLAGS: -D warnings shell: bash -euxo pipefail {0} run: cargo check --tests --workspace - name: Run tests - env: - RUSTFLAGS: -D warnings shell: bash -euxo pipefail {0} run: cargo nextest run --workspace --no-fail-fast diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65475a41b9..482e1c51f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - self-hosted - test steps: + - name: Set up default .cargo/config.toml + run: echo "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml + - name: Checkout repo uses: actions/checkout@v3 with: From 8a6d3094c43e6ed3fb69e74fc9c301ef9860f996 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:37:41 +0100 Subject: [PATCH 12/27] Change tabs to spaces --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 482e1c51f3..bffc06075a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - test steps: - name: Set up default .cargo/config.toml - run: echo "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml + run: echo "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml - name: Checkout repo uses: actions/checkout@v3 From 6f8e03470ca882867e729aad8f15167f22a8609a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:41:26 +0100 Subject: [PATCH 13/27] Use printf instead of echo --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bffc06075a..9af0ab57a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - test steps: - name: Set up default .cargo/config.toml - run: echo "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml + run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml - name: Checkout repo uses: actions/checkout@v3 From 492c3a1e83118af28b6a9d534efadeeb1525186d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 15:14:24 +0100 Subject: [PATCH 14/27] Bump artifact size limit for CI to 100GB --- .github/actions/run_tests/action.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release_nightly.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 8c1e09ee74..1ea51a06a6 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -19,7 +19,7 @@ runs: - name: Limit target directory size shell: bash -euxo pipefail {0} - run: script/clear-target-dir-if-larger-than 70 + run: script/clear-target-dir-if-larger-than 100 - name: Run check shell: bash -euxo pipefail {0} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9af0ab57a9..208d538976 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: submodules: "recursive" - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 70 + run: script/clear-target-dir-if-larger-than 100 - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 447e928866..7b08c52c61 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -79,7 +79,7 @@ jobs: submodules: "recursive" - name: Limit target directory size - run: script/clear-target-dir-if-larger-than 70 + run: script/clear-target-dir-if-larger-than 100 - name: Set release channel to nightly run: | From bd4a710cef2bbb0532abfe6d8c7528a7c05c4bcd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 17:50:34 +0100 Subject: [PATCH 15/27] Use interactivity's base style for UniformList --- crates/gpui2/src/elements/uniform_list.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index b24b3935fa..3c3222c8a0 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -22,8 +22,8 @@ where V: Render, { let id = id.into(); - let mut style = StyleRefinement::default(); - style.overflow.y = Some(Overflow::Hidden); + let mut base_style = StyleRefinement::default(); + base_style.overflow.y = Some(Overflow::Scroll); let render_range = move |range, cx: &mut WindowContext| { view.update(cx, |this, cx| { @@ -36,12 +36,12 @@ where UniformList { id: id.clone(), - style, item_count, item_to_measure_index: 0, render_items: Box::new(render_range), interactivity: Interactivity { element_id: Some(id.into()), + base_style, ..Default::default() }, scroll_handle: None, @@ -50,7 +50,6 @@ where pub struct UniformList { id: ElementId, - style: StyleRefinement, item_count: usize, item_to_measure_index: usize, render_items: @@ -91,7 +90,7 @@ impl UniformListScrollHandle { impl Styled for UniformList { fn style(&mut self) -> &mut StyleRefinement { - &mut self.style + &mut self.interactivity.base_style } } From 8aaa46a1b68ea99733a7331562ab7de7cae1b74f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 17:58:00 +0100 Subject: [PATCH 16/27] Track scroll in editor's context menu --- crates/editor2/src/editor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 690f6c2278..fa5f4dfa42 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1370,6 +1370,7 @@ impl CompletionsMenu { .collect() }, ) + .track_scroll(self.scroll_handle.clone()) .with_width_from_item(widest_completion_ix); list.render_into_any() @@ -1587,6 +1588,7 @@ impl CodeActionsMenu { .elevation_1(cx) .px_2() .py_1() + .track_scroll(self.scroll_handle.clone()) .with_width_from_item( self.actions .iter() From eaf90a4fbd24423da6f9202da65f42a0d696f32d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 18:32:02 +0100 Subject: [PATCH 17/27] Fix drawing uniform list elements when scrolling --- crates/gpui2/src/elements/uniform_list.rs | 42 +++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 3c3222c8a0..3649727648 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement, - InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Render, RenderOnce, Size, - StyleRefinement, Styled, View, ViewContext, WindowContext, + point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, + ElementId, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point, + Render, RenderOnce, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -210,31 +210,31 @@ impl Element for UniformList { scroll_offset: shared_scroll_offset, }); } - let visible_item_count = if item_height > px(0.) { - (padded_bounds.size.height / item_height).ceil() as usize + 1 - } else { - 0 - }; let first_visible_element_ix = (-scroll_offset.y / item_height).floor() as usize; + let last_visible_element_ix = + ((-scroll_offset.y + padded_bounds.size.height) / item_height).ceil() + as usize; let visible_range = first_visible_element_ix - ..cmp::min( - first_visible_element_ix + visible_item_count, - self.item_count, - ); + ..cmp::min(last_visible_element_ix, self.item_count); let items = (self.render_items)(visible_range.clone(), cx); cx.with_z_index(1, |cx| { - for (item, ix) in items.into_iter().zip(visible_range) { - let item_origin = padded_bounds.origin - + point(px(0.), item_height * ix + scroll_offset.y); - let available_space = size( - AvailableSpace::Definite(padded_bounds.size.width), - AvailableSpace::Definite(item_height), - ); - item.draw(item_origin, available_space, cx); - } + let content_mask = ContentMask { + bounds: padded_bounds, + }; + cx.with_content_mask(Some(content_mask), |cx| { + for (item, ix) in items.into_iter().zip(visible_range) { + let item_origin = padded_bounds.origin + + point(px(0.), item_height * ix + scroll_offset.y); + let available_space = size( + AvailableSpace::Definite(padded_bounds.size.width), + AvailableSpace::Definite(item_height), + ); + item.draw(item_origin, available_space, cx); + } + }); }); } }) From 2c8d243d2223ab7e5ab3bfd1ba06cb1d49269e3a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 22 Nov 2023 12:41:06 -0500 Subject: [PATCH 18/27] Comment out `todo!()` to fix panic when opening context menus --- crates/project_panel2/src/project_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 4d1a6ee8f7..0550fc7bd2 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -371,7 +371,7 @@ impl ProjectPanel { _entry_id: ProjectEntryId, _cx: &mut ViewContext, ) { - todo!() + // todo!() // let project = self.project.read(cx); // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { From 031fca4105b84b6c1227e238a850773ca207f829 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 22 Nov 2023 12:41:29 -0500 Subject: [PATCH 19/27] Simplify `ContextMenu` by not storing list components --- crates/terminal_view2/src/terminal_view.rs | 9 ++---- crates/ui2/src/components/context_menu.rs | 32 +++++++++---------- crates/ui2/src/components/list.rs | 17 ---------- .../src/components/stories/context_menu.rs | 20 +++++------- crates/workspace2/src/dock.rs | 9 ++---- 5 files changed, 29 insertions(+), 58 deletions(-) diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 5a5f74f9e1..9f3ed31388 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -31,7 +31,7 @@ use workspace::{ notifications::NotifyResultExt, register_deserializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem}, - ui::{ContextMenu, Icon, IconElement, Label, ListItem}, + ui::{ContextMenu, Icon, IconElement, Label}, CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; @@ -299,11 +299,8 @@ impl TerminalView { cx: &mut ViewContext, ) { self.context_menu = Some(ContextMenu::build(cx, |menu, _| { - menu.action(ListItem::new("clear", Label::new("Clear")), Box::new(Clear)) - .action( - ListItem::new("close", Label::new("Close")), - Box::new(CloseActiveItem { save_intent: None }), - ) + menu.action("Clear", Box::new(Clear)) + .action("Close", Box::new(CloseActiveItem { save_intent: None })) })); dbg!(&position); // todo!() diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 8bb5b2e5d2..81cc3892ee 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; -use crate::{prelude::*, v_stack, List}; +use crate::{prelude::*, v_stack, Label, List}; use crate::{ListItem, ListSeparator, ListSubHeader}; use gpui::{ overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase, @@ -10,9 +10,9 @@ use gpui::{ }; pub enum ContextMenuItem { - Separator(ListSeparator), - Header(ListSubHeader), - Entry(ListItem, Rc), + Separator, + Header(SharedString), + Entry(SharedString, Rc), } pub struct ContextMenu { @@ -46,29 +46,30 @@ impl ContextMenu { } pub fn header(mut self, title: impl Into) -> Self { - self.items - .push(ContextMenuItem::Header(ListSubHeader::new(title))); + self.items.push(ContextMenuItem::Header(title.into())); self } pub fn separator(mut self) -> Self { - self.items.push(ContextMenuItem::Separator(ListSeparator)); + self.items.push(ContextMenuItem::Separator); self } pub fn entry( mut self, - view: ListItem, + label: impl Into, on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, ) -> Self { self.items - .push(ContextMenuItem::Entry(view, Rc::new(on_click))); + .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click))); self } - pub fn action(self, view: ListItem, action: Box) -> Self { + pub fn action(self, label: impl Into, action: Box) -> Self { // todo: add the keybindings to the list entry - self.entry(view, move |_, cx| cx.dispatch_action(action.boxed_clone())) + self.entry(label.into(), move |_, cx| { + cx.dispatch_action(action.boxed_clone()) + }) } pub fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { @@ -104,16 +105,15 @@ impl Render for ContextMenu { // .border_color(cx.theme().colors().border) .child( List::new().children(self.items.iter().map(|item| match item { - ContextMenuItem::Separator(separator) => { - separator.clone().render_into_any() + ContextMenuItem::Separator => ListSeparator::new().render_into_any(), + ContextMenuItem::Header(header) => { + ListSubHeader::new(header.clone()).render_into_any() } - ContextMenuItem::Header(header) => header.clone().render_into_any(), ContextMenuItem::Entry(entry, callback) => { let callback = callback.clone(); let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss)); - entry - .clone() + ListItem::new(entry.clone(), Label::new(entry.clone())) .on_click(move |event, cx| { callback(event, cx); dismiss(event, cx) diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 0266ae3342..2cf37d3b65 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -254,23 +254,6 @@ pub struct ListItem { on_click: Option>, } -impl Clone for ListItem { - fn clone(&self) -> Self { - Self { - id: self.id.clone(), - disabled: self.disabled, - indent_level: self.indent_level, - label: self.label.clone(), - left_slot: self.left_slot.clone(), - overflow: self.overflow, - size: self.size, - toggle: self.toggle, - variant: self.variant, - on_click: self.on_click.clone(), - } - } -} - impl ListItem { pub fn new(id: impl Into, label: Label) -> Self { Self { diff --git a/crates/ui2/src/components/stories/context_menu.rs b/crates/ui2/src/components/stories/context_menu.rs index dd0bc03a21..98faea70aa 100644 --- a/crates/ui2/src/components/stories/context_menu.rs +++ b/crates/ui2/src/components/stories/context_menu.rs @@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View}; use story::Story; use crate::prelude::*; -use crate::{menu_handle, ContextMenu, Label, ListItem}; +use crate::{menu_handle, ContextMenu, Label}; actions!(PrintCurrentDate, PrintBestFood); @@ -10,17 +10,13 @@ fn build_menu(cx: &mut WindowContext, header: impl Into) -> View Date: Wed, 22 Nov 2023 12:44:51 -0500 Subject: [PATCH 20/27] Use `children` for `ListItem`s --- crates/ui2/src/components/context_menu.rs | 3 ++- crates/ui2/src/components/list.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 81cc3892ee..2473bff610 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -113,7 +113,8 @@ impl Render for ContextMenu { let callback = callback.clone(); let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss)); - ListItem::new(entry.clone(), Label::new(entry.clone())) + ListItem::new(entry.clone()) + .child(Label::new(entry.clone())) .on_click(move |event, cx| { callback(event, cx); dismiss(event, cx) diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 2cf37d3b65..7319640b9e 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -245,28 +245,28 @@ pub struct ListItem { // TODO: Reintroduce this // disclosure_control_style: DisclosureControlVisibility, indent_level: u32, - label: Label, left_slot: Option, overflow: OverflowStyle, size: ListEntrySize, toggle: Toggle, variant: ListItemVariant, on_click: Option>, + children: SmallVec<[AnyElement; 2]>, } impl ListItem { - pub fn new(id: impl Into, label: Label) -> Self { + pub fn new(id: impl Into) -> Self { Self { id: id.into(), disabled: false, indent_level: 0, - label, left_slot: None, overflow: OverflowStyle::Hidden, size: ListEntrySize::default(), toggle: Toggle::NotToggleable, variant: ListItemVariant::default(), on_click: Default::default(), + children: SmallVec::new(), } } @@ -377,11 +377,17 @@ impl Component for ListItem { .relative() .child(disclosure_control(self.toggle)) .children(left_content) - .child(self.label), + .children(self.children), ) } } +impl ParentElement for ListItem { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + #[derive(RenderOnce, Clone)] pub struct ListSeparator; From 7b0b87380d18861d2260ae76a4b6547a5b00938d Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 22 Nov 2023 12:57:32 -0500 Subject: [PATCH 21/27] v0.115.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f36f2445d..9cf13b56c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11493,7 +11493,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.114.0" +version = "0.115.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ab8d5b7efe..0512eaca7a 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.114.0" +version = "0.115.0" publish = false [lib] From 524f892fb0a6d8e19c6d3ce3e0d0f8aac555d27b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 22 Nov 2023 19:02:44 +0100 Subject: [PATCH 22/27] Correctly swap position of context menu --- crates/editor2/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 7f6135087a..2cc6749e6b 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1051,7 +1051,7 @@ impl EditorElement { } if list_origin.y + list_height > text_bounds.lower_right().y { - list_origin.y -= layout.position_map.line_height - list_height; + list_origin.y -= layout.position_map.line_height + list_height; } context_menu.draw(list_origin, available_space, cx); From 10c4df20e93bd91ffb7be1a4fa514fd25c0f4477 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 22 Nov 2023 13:05:29 -0500 Subject: [PATCH 23/27] collab 0.29.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cf13b56c1..8a279b2450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1664,7 +1664,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index dea6e09245..bbaf521e15 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.28.0" +version = "0.29.0" publish = false [[bin]] From 9abce4bdd908fa288d2daa6de1dfbab44358375b Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 22 Nov 2023 13:16:52 -0500 Subject: [PATCH 24/27] zed1: Cancel completion resolution when new list Co-Authored-By: Max Brunsfeld --- crates/editor/src/editor.rs | 43 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2558aec121..9e01814698 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1001,17 +1001,18 @@ impl CompletionsMenu { fn pre_resolve_completion_documentation( &self, - project: Option>, + editor: &Editor, cx: &mut ViewContext, - ) { + ) -> Option> { let settings = settings::get::(cx); if !settings.show_completion_documentation { - return; + return None; } - let Some(project) = project else { - return; + let Some(project) = editor.project.clone() else { + return None; }; + let client = project.read(cx).client(); let language_registry = project.read(cx).languages().clone(); @@ -1021,7 +1022,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); - cx.spawn(move |this, mut cx| async move { + Some(cx.spawn(move |this, mut cx| async move { if is_remote { let Some(project_id) = project_id else { log::error!("Remote project without remote_id"); @@ -1083,8 +1084,7 @@ impl CompletionsMenu { _ = this.update(&mut cx, |_, cx| cx.notify()); } } - }) - .detach(); + })) } fn attempt_resolve_selected_completion_documentation( @@ -3580,7 +3580,8 @@ impl Editor { let id = post_inc(&mut self.next_completion_id); let task = cx.spawn(|this, mut cx| { async move { - let menu = if let Some(completions) = completions.await.log_err() { + let completions = completions.await.log_err(); + let (menu, pre_resolve_task) = if let Some(completions) = completions { let mut menu = CompletionsMenu { id, initial_position: position, @@ -3601,21 +3602,26 @@ impl Editor { selected_item: 0, list: Default::default(), }; + menu.filter(query.as_deref(), cx.background()).await; + if menu.matches.is_empty() { - None + (None, None) } else { - _ = this.update(&mut cx, |editor, cx| { - menu.pre_resolve_completion_documentation(editor.project.clone(), cx); - }); - Some(menu) + let pre_resolve_task = this + .update(&mut cx, |editor, cx| { + menu.pre_resolve_completion_documentation(editor, cx) + }) + .ok() + .flatten(); + (Some(menu), pre_resolve_task) } } else { - None + (None, None) }; this.update(&mut cx, |this, cx| { - this.completion_tasks.retain(|(task_id, _)| *task_id > id); + this.completion_tasks.retain(|(task_id, _)| *task_id >= id); let mut context_menu = this.context_menu.write(); match context_menu.as_ref() { @@ -3647,10 +3653,15 @@ impl Editor { } })?; + if let Some(pre_resolve_task) = pre_resolve_task { + pre_resolve_task.await; + } + Ok::<_, anyhow::Error>(()) } .log_err() }); + self.completion_tasks.push((id, task)); } From fa74c49dbbccc113e6a8d9f464a04e1193e30c2f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:23:58 +0100 Subject: [PATCH 25/27] Add dummy call handler for tests --- crates/collab2/src/tests/test_server.rs | 1 + crates/workspace2/src/workspace2.rs | 32 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/collab2/src/tests/test_server.rs b/crates/collab2/src/tests/test_server.rs index 090a32d4ca..f620662f71 100644 --- a/crates/collab2/src/tests/test_server.rs +++ b/crates/collab2/src/tests/test_server.rs @@ -221,6 +221,7 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), + call_factory: |_, _| Box::new(workspace::TestCallHandler), }); cx.update(|cx| { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index e1e79c4d3e..b09b47d24c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -322,6 +322,36 @@ struct Follower { peer_id: PeerId, } +#[cfg(any(test, feature = "test-support"))] +pub struct TestCallHandler; + +#[cfg(any(test, feature = "test-support"))] +impl CallHandler for TestCallHandler { + fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext) -> Option<(bool, bool)> { + None + } + + fn shared_screen_for_peer( + &self, + peer_id: PeerId, + pane: &View, + cx: &mut ViewContext, + ) -> Option> { + None + } + + fn room_id(&self, cx: &AppContext) -> Option { + None + } + + fn hang_up(&self, cx: AsyncWindowContext) -> Result>> { + anyhow::bail!("TestCallHandler should not be hanging up") + } + + fn active_project(&self, cx: &AppContext) -> Option> { + None + } +} impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { @@ -352,6 +382,7 @@ impl AppState { workspace_store, node_runtime: FakeNodeRuntime::new(), build_window_options: |_, _, _| Default::default(), + call_factory: |_, _| Box::new(TestCallHandler), }) } } @@ -3298,6 +3329,7 @@ impl Workspace { fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), + call_factory: |_, _| Box::new(TestCallHandler), }); let workspace = Self::new(0, project, app_state, cx); workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); From b45234eecea255cc3fef12b1c5fbf58dba40892e Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:24:38 +0100 Subject: [PATCH 26/27] Fix warnings in unimplemented function --- crates/call2/src/call2.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 18576d4657..9579552d5a 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -552,14 +552,14 @@ impl CallHandler for Call { fn shared_screen_for_peer( &self, peer_id: PeerId, - pane: &View, + _pane: &View, cx: &mut ViewContext, ) -> Option> { let (call, _) = self.active_call.as_ref()?; let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(peer_id)?; - let track = participant.video_tracks.values().next()?.clone(); - let user = participant.user.clone(); + let _track = participant.video_tracks.values().next()?.clone(); + let _user = participant.user.clone(); todo!(); // for item in pane.read(cx).items_of_type::() { // if item.read(cx).peer_id == peer_id { From f0c7b3e6ee8618441386b7bdb6096512c8a2943d Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 22 Nov 2023 14:03:43 -0500 Subject: [PATCH 27/27] Update copilot when we are the last task --- crates/editor/src/editor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9e01814698..17712b7e78 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3642,10 +3642,10 @@ impl Editor { drop(context_menu); this.discard_copilot_suggestion(cx); cx.notify(); - } else if this.completion_tasks.is_empty() { - // If there are no more completion tasks and the last menu was - // empty, we should hide it. If it was already hidden, we should - // also show the copilot suggestion when available. + } else if this.completion_tasks.len() <= 1 { + // If there are no more completion tasks (omitting ourself) and + // the last menu was empty, we should hide it. If it was already + // hidden, we should also show the copilot suggestion when available. drop(context_menu); if this.hide_context_menu(cx).is_none() { this.update_visible_copilot_suggestion(cx);