diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index de5eadb61a..1ea51a06a6 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -19,16 +19,12 @@ 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 - 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..208d538976 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: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml + - name: Checkout repo uses: actions/checkout@v3 with: @@ -87,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: | diff --git a/Cargo.lock b/Cargo.lock index 8f36f2445d..99d4827966 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,6 +1186,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-broadcast", + "async-trait", "audio2", "client2", "collections", @@ -1204,6 +1205,7 @@ dependencies = [ "serde_json", "settings2", "util", + "workspace2", ] [[package]] @@ -1664,7 +1666,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.28.0" +version = "0.29.0" dependencies = [ "anyhow", "async-trait", @@ -11381,6 +11383,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-recursion 1.0.5", + "async-trait", "bincode", "call2", "client2", @@ -11493,7 +11496,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.114.0" +version = "0.115.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 9e13463680..43e19b4ccb 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -31,7 +31,8 @@ media = { path = "../media" } 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 1f11e0650d..9579552d5a 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,6 +510,116 @@ pub fn report_call_event_for_channel( ) } +pub struct Call { + active_call: Option<(Model, Vec)>, + parent_workspace: WeakView, +} + +impl Call { + 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)); + } + Box::new(Self { + active_call, + parent_workspace, + }) + } + fn on_active_call_event( + workspace: &mut Workspace, + _: Model, + event: &room::Event, + cx: &mut ViewContext, + ) { + match event { + room::Event::ParticipantLocationChanged { participant_id } + | room::Event::RemoteVideoTracksChanged { participant_id } => { + workspace.leader_updated(*participant_id, cx); + } + _ => {} + } + } +} + +#[async_trait(?Send)] +impl CallHandler for Call { + 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 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() + } + 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)] mod test { use gpui::TestAppContext; 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]] 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/editor/src/editor.rs b/crates/editor/src/editor.rs index 2558aec121..17712b7e78 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() { @@ -3636,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); @@ -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)); } diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index fa7bfd77cc..fa5f4dfa42 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}; @@ -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,202 @@ impl CompletionsMenu { workspace: Option>, cx: &mut ViewContext, ) -> AnyElement { - todo!("old implementation below") + 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( + cx.view().clone(), + "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) + .whitespace_nowrap() + .overflow_hidden() + .bg(gpui::green()) + .hover(|style| style.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.)) + }) + .collect() + }, + ) + .track_scroll(self.scroll_handle.clone()) + .with_width_from_item(widest_completion_ix); + + list.render_into_any() + // 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( @@ -1594,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() @@ -9405,6 +9400,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!(), @@ -9418,6 +9414,7 @@ impl Render for Editor { font_style: FontStyle::Normal, line_height: relative(settings.buffer_line_height.value()), underline: None, + white_space: WhiteSpace::Normal, }, }; @@ -10126,49 +10123,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; diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index add9c9ad33..2cc6749e6b 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, @@ -871,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); @@ -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..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() @@ -174,10 +178,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 { @@ -194,7 +195,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 { diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index b24b3935fa..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}; @@ -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 } } @@ -211,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); + } + }); }); } }) 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 { diff --git a/crates/language2/src/buffer.rs b/crates/language2/src/buffer.rs index 51ed192b99..26ee93adef 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, @@ -155,12 +156,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, } 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) { 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..2473bff610 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,16 @@ 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()) + .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 0266ae3342..7319640b9e 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -245,45 +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>, -} - -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(), - } - } + 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(), } } @@ -394,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; 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, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -137,7 +135,6 @@ impl PaneGroup { project, 0, follower_states, - active_call, active_pane, zoomed, app_state, @@ -199,7 +196,6 @@ impl Member { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -235,7 +231,6 @@ impl Member { project, basis + 1, follower_states, - active_call, active_pane, zoomed, app_state, @@ -558,7 +553,6 @@ impl PaneAxis { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, - active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -580,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..b09b47d24c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -16,7 +16,7 @@ mod toolbar; mod workspace_settings; use anyhow::{anyhow, Context as _, Result}; -use call2::ActiveCall; +use async_trait::async_trait; use client2::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, @@ -33,8 +33,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; @@ -210,7 +210,6 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(app_state: Arc, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); - // 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, @@ -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), }) } } @@ -408,6 +439,23 @@ pub enum Event { WorkspaceCreated(WeakView), } +#[async_trait(?Send)] +pub trait CallHandler { + 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 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>; +} + pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -428,10 +476,10 @@ pub struct Workspace { titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, + call_handler: Box, follower_states: HashMap, FollowerState>, 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, @@ -459,6 +507,7 @@ struct FollowerState { enum WorkspaceBounds {} +type CallFactory = fn(WeakView, &mut ViewContext) -> Box; impl Workspace { pub fn new( workspace_id: WorkspaceId, @@ -550,9 +599,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 +644,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| { @@ -655,7 +706,8 @@ impl Workspace { follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, - active_call, + + call_handler: (app_state.call_factory)(weak_handle.clone(), cx), database_id: workspace_id, app_state, _observe_current_user, @@ -1102,7 +1154,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 +1165,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(); } } @@ -2391,19 +2443,19 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let state = self.follower_states.remove(pane)?; + 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 { 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 { @@ -2762,8 +2814,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) }) } @@ -2771,31 +2824,12 @@ impl Workspace { self.follower_states.get(pane).map(|state| state.leader_id) } - fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + pub 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 (leader_in_this_project, leader_in_this_app) = + self.call_handler.peer_state(leader_id, cx)?; 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; @@ -2825,8 +2859,8 @@ impl Workspace { 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) + pane.update(cx, |pane, mut cx| { + pane.add_item(item.boxed_clone(), false, false, None, &mut cx) }); } @@ -2886,25 +2920,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 } @@ -3314,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)); @@ -3672,7 +3688,6 @@ impl Render for Workspace { .child(self.center.render( &self.project, &self.follower_states, - self.active_call(), &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -3842,14 +3857,10 @@ impl WorkspaceStore { pub fn update_followers( &self, project_id: Option, + room_id: u64, 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 follower_ids: Vec<_> = self .followers .iter() @@ -3885,9 +3896,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| { 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] diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index c1db4eace4..49024bad4d 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, @@ -355,7 +356,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(); - // todo!(welcome) //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { //todo!()