Detailed changes
@@ -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
@@ -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') }}
@@ -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: |
@@ -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",
@@ -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
@@ -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<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
CallSettings::register(cx);
@@ -505,6 +510,116 @@ pub fn report_call_event_for_channel(
)
}
+pub struct Call {
+ active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
+ parent_workspace: WeakView<Workspace>,
+}
+
+impl Call {
+ pub fn new(
+ parent_workspace: WeakView<Workspace>,
+ cx: &mut ViewContext<'_, Workspace>,
+ ) -> Box<dyn CallHandler> {
+ let mut active_call = None;
+ if cx.has_global::<Model<ActiveCall>>() {
+ let call = cx.global::<Model<ActiveCall>>().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<ActiveCall>,
+ event: &room::Event,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ 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<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Box<dyn ItemHandle>> {
+ 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::<SharedScreen>() {
+ // 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<u64> {
+ Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
+ }
+ fn hang_up(&self, mut cx: AsyncWindowContext) -> Result<Task<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<WeakModel<Project>> {
+ ActiveCall::global(cx).read(cx).location().cloned()
+ }
+ fn peer_state(
+ &mut self,
+ leader_id: PeerId,
+ cx: &mut ViewContext<Workspace>,
+ ) -> 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;
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
-version = "0.28.0"
+version = "0.29.0"
publish = false
[[bin]]
@@ -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| {
@@ -1001,17 +1001,18 @@ impl CompletionsMenu {
fn pre_resolve_completion_documentation(
&self,
- project: Option<ModelHandle<Project>>,
+ editor: &Editor,
cx: &mut ViewContext<Editor>,
- ) {
+ ) -> Option<Task<()>> {
let settings = settings::get::<EditorSettings>(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));
}
@@ -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,207 +1224,201 @@ impl CompletionsMenu {
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
- todo!("old implementation below")
- }
+ let settings = EditorSettings::get_global(cx);
+ let show_completion_documentation = settings.show_completion_documentation;
- // enum CompletionTag {}
+ 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();
+ }
+ }
- // let settings = EditorSettings>(cx);
- // let show_completion_documentation = settings.show_completion_documentation;
+ len
+ })
+ .map(|(ix, _)| ix);
- // 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 completions = self.completions.clone();
+ let matches = self.matches.clone();
+ let selected_item = self.selected_item;
- // let mut len = completion.label.text.chars().count();
- // if let Some(Documentation::SingleLine(text)) = documentation {
- // if show_completion_documentation {
- // len += text.chars().count();
- // }
- // }
+ let list = uniform_list(
+ cx.view().clone(),
+ "completions",
+ matches.len(),
+ move |editor, range, cx| {
+ let start_ix = range.start;
+ let completions_guard = completions.read();
- // len
- // })
- // .map(|(ix, _)| ix);
+ 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 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
- // };
+ let documentation = if show_completion_documentation {
+ &completion.documentation
+ } else {
+ &None
+ };
- // items.push(
- // MouseEventHandler::new::<CompletionTag, _>(
- // 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::<MultiLineDocumentation>(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::<MultiLineDocumentation>(0, None, cx)
- // .with_child(render_parsed_markdown::<MultiLineDocumentation>(
- // 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()
- // }
+ // 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::<CompletionTag, _>(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::<MultiLineDocumentation>(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::<MultiLineDocumentation>(0, None, cx)
+ // .with_child(render_parsed_markdown::<MultiLineDocumentation>(
+ // 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 {
@@ -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<Item = (Range<usize>, 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<Item = (Range<usize>, 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<usize>, 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<usize>, 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<Item = &'a str> + 'a {
let mut index = 0;
@@ -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<Pixels>,
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<Pixels>,
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<Pixels>,
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<Editor>) -> 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<Editor>) -> 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<u32>,
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
@@ -1469,483 +1648,469 @@ impl EditorElement {
fn compute_layout(
&mut self,
- editor: &mut Editor,
- cx: &mut ViewContext<'_, Editor>,
mut bounds: Bounds<Pixels>,
+ 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");
- // }
-
- let snapshot = editor.snapshot(cx);
- let style = self.style.clone();
+ 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 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_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;
+ };
- 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_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),
+ };
- 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
+ }
};
- if editor.set_wrap_width(Some(wrap_width), cx) {
- editor.snapshot(cx)
+ let wrap_guides = editor
+ .wrap_guides(cx)
+ .iter()
+ .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+ .collect::<SmallVec<[_; 2]>>();
+
+ let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
+ // todo!("this should happen during layout")
+ let editor_mode = snapshot.mode;
+ if let EditorMode::AutoHeight { max_lines } = editor_mode {
+ todo!()
+ // size.set_y(
+ // scroll_height
+ // .min(constraint.max_along(Axis::Vertical))
+ // .max(constraint.min_along(Axis::Vertical))
+ // .max(line_height)
+ // .min(line_height * max_lines as f32),
+ // )
+ } else if let EditorMode::SingleLine = editor_mode {
+ bounds.size.height = line_height.min(bounds.size.height);
+ }
+ // todo!()
+ // else if size.y.is_infinite() {
+ // // size.set_y(scroll_height);
+ // }
+ //
+ let gutter_size = size(gutter_width, bounds.size.height);
+ let text_size = size(text_width, bounds.size.height);
+
+ 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::<SmallVec<[_; 2]>>();
-
- let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
- // todo!("this should happen during layout")
- let editor_mode = snapshot.mode;
- if let EditorMode::AutoHeight { max_lines } = editor_mode {
- todo!()
- // size.set_y(
- // scroll_height
- // .min(constraint.max_along(Axis::Vertical))
- // .max(constraint.min_along(Axis::Vertical))
- // .max(line_height)
- // .min(line_height * max_lines as f32),
- // )
- } else if let EditorMode::SingleLine = editor_mode {
- bounds.size.height = line_height.min(bounds.size.height);
- }
- // todo!()
- // else if size.y.is_infinite() {
- // // size.set_y(scroll_height);
- // }
- //
- let gutter_size = size(gutter_width, bounds.size.height);
- let text_size = size(text_width, bounds.size.height);
-
- 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 mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
+ let mut active_rows = BTreeMap::new();
+ let is_singleton = editor.is_singleton(cx);
- let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = 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 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<Selection<Point>> = editor
+ .selections
+ .disjoint_in_range(start_anchor..end_anchor, cx);
+ local_selections.extend(editor.selections.pending(cx));
+ let mut layouts = Vec::new();
+ let newest = editor.selections.newest(cx);
+ for selection in local_selections.drain(..) {
+ let is_empty = selection.start == selection.end;
+ let is_newest = selection == newest;
+
+ let layout = SelectionLayout::new(
+ selection,
+ editor.selections.line_mode,
+ editor.cursor_shape,
+ &snapshot.display_snapshot,
+ is_newest,
+ true,
+ );
+ if is_newest {
+ newest_selection_head = Some(layout.head);
+ }
- let mut newest_selection_head = None;
-
- if editor.show_local_selections {
- let mut local_selections: Vec<Selection<Point>> = editor
- .selections
- .disjoint_in_range(start_anchor..end_anchor, cx);
- local_selections.extend(editor.selections.pending(cx));
- let mut layouts = Vec::new();
- let newest = editor.selections.newest(cx);
- for selection in local_selections.drain(..) {
- let is_empty = selection.start == selection.end;
- let is_newest = selection == newest;
-
- let layout = SelectionLayout::new(
- selection,
- editor.selections.line_mode,
- editor.cursor_shape,
- &snapshot.display_snapshot,
- is_newest,
- true,
- );
- if is_newest {
- newest_selection_head = Some(layout.head);
+ for row in cmp::max(layout.active_rows.start, start_row)
+ ..=cmp::min(layout.active_rows.end, end_row)
+ {
+ let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
+ *contains_non_empty_selection |= !is_empty;
+ }
+ layouts.push(layout);
}
- 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));
}
- 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);
+ 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()
- };
-
- // Don't re-render the leader's selections, since the local selections
- // match theirs.
- if Some(selection.peer_id) == editor.leader_peer_id {
- continue;
- }
+ 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()
+ };
- 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,
- ));
- }
+ // Don't re-render the leader's selections, since the local selections
+ // match theirs.
+ if Some(selection.peer_id) == editor.leader_peer_id {
+ continue;
+ }
- selections.extend(remote_selections.into_values());
- }
+ 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,
+ ));
+ }
- 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()
+ selections.extend(remote_selections.into_values());
}
- 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::<Point>(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 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 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 head_for_relative = newest_selection_head.unwrap_or_else(|| {
+ let newest = editor.selections.newest::<Point>(cx);
+ SelectionLayout::new(
+ newest,
+ editor.selections.line_mode,
+ editor.cursor_shape,
+ &snapshot.display_snapshot,
+ true,
+ true,
+ )
+ .head
+ });
- let (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
- };
+ 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;
+ }
+ }
- if clamped || autoscrolled {
- snapshot = editor.snapshot(cx);
- }
+ 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 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 (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 active = matches!(
- editor.context_menu.read().as_ref(),
- Some(crate::ContextMenu::CodeActions(_))
- );
+ let scroll_max = point(
+ f32::from((scroll_width - text_size.width) / em_width).max(0.0),
+ max_row as f32,
+ );
- code_actions_indicator = editor
- .render_code_actions_indicator(&style, active, cx)
- .map(|element| CodeActionsIndicator {
- row: newest_selection_head.row(),
- button: element,
- });
+ 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 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,
- )
- });
+ 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);
+ }
- // todo!("context_menu")
- // if let Some((_, context_menu)) = context_menu.as_mut() {
- // context_menu.layout(
- // SizeConstraint {
- // min: gpui::Point::<Pixels>::zero(),
- // max: point(
- // cx.window_size().x * 0.7,
- // (12. * line_height).min((size.y - line_height) / 2.),
- // ),
- // },
- // editor,
- // cx,
- // );
- // }
+ let active = matches!(
+ editor.context_menu.read().as_ref(),
+ Some(crate::ContextMenu::CodeActions(_))
+ );
- // 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::<Pixels>::zero(),
- // max: point(
- // (120. * em_width) // Default size
- // .min(size.x / 2.) // Shrink to half of the editor width
- // .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
- // (16. * line_height) // Default size
- // .min(size.y / 2.) // Shrink to half of the editor height
- // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
- // ),
- // },
- // editor,
- // cx,
- // );
- // }
- // }
+ code_actions_indicator = editor
+ .render_code_actions_indicator(&style, active, cx)
+ .map(|element| CodeActionsIndicator {
+ row: newest_selection_head.row(),
+ button: element,
+ });
+ }
+ }
- 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();
+ 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,
+ )
+ });
- 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,
- }
+ // 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::<Pixels>::zero(),
+ // max: point(
+ // (120. * em_width) // Default size
+ // .min(size.x / 2.) // Shrink to half of the editor width
+ // .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
+ // (16. * line_height) // Default size
+ // .min(size.y / 2.) // Shrink to half of the editor height
+ // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
+ // ),
+ // },
+ // editor,
+ // cx,
+ // );
+ // }
+ // }
+
+ let invisible_symbol_font_size = 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,
+ }
+ })
}
#[allow(clippy::too_many_arguments)]
fn layout_blocks(
- &mut self,
+ &self,
rows: Range<u32>,
snapshot: &EditorSnapshot,
editor_width: Pixels,
@@ -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 {
@@ -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);
+ }
+ });
});
}
})
@@ -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<UnderlineStyle>,
+ 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,
}
}
}
@@ -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 {
@@ -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<LanguageRegistry>,
+ language: Option<Arc<Language>>,
+) -> 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<Anchor>,
pub new_text: String,
pub label: CodeLabel,
pub server_id: LanguageServerId,
+ pub documentation: Option<Documentation>,
pub lsp_completion: lsp::CompletionItem,
}
@@ -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,
})
@@ -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<lsp::CompletionResponse>,
- _: Model<Project>,
+ project: Model<Project>,
buffer: Model<Buffer>,
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,
}
@@ -371,7 +371,7 @@ impl ProjectPanel {
_entry_id: ProjectEntryId,
_cx: &mut ViewContext<Self>,
) {
- todo!()
+ // todo!()
// let project = self.project.read(cx);
// let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
@@ -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>,
) {
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!()
@@ -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<dyn Fn(&ClickEvent, &mut WindowContext)>),
+ Separator,
+ Header(SharedString),
+ Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
}
pub struct ContextMenu {
@@ -46,29 +46,30 @@ impl ContextMenu {
}
pub fn header(mut self, title: impl Into<SharedString>) -> 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<SharedString>,
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<dyn Action>) -> Self {
+ pub fn action(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> 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<Self>) {
@@ -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)
@@ -245,45 +245,28 @@ pub struct ListItem {
// TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility,
indent_level: u32,
- label: Label,
left_slot: Option<GraphicSlot>,
overflow: OverflowStyle,
size: ListEntrySize,
toggle: Toggle,
variant: ListItemVariant,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
-}
-
-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<ElementId>, label: Label) -> Self {
+ pub fn new(id: impl Into<ElementId>) -> 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;
@@ -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<SharedString>) -> View<C
ContextMenu::build(cx, |menu, _| {
menu.header(header)
.separator()
- .entry(
- ListItem::new("Print current time", Label::new("Print current time")),
- |v, cx| {
- println!("dispatching PrintCurrentTime action");
- cx.dispatch_action(PrintCurrentDate.boxed_clone())
- },
- )
- .entry(
- ListItem::new("Print best food", Label::new("Print best food")),
- |v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
- )
+ .entry("Print current time", |v, cx| {
+ println!("dispatching PrintCurrentTime action");
+ cx.dispatch_action(PrintCurrentDate.boxed_clone())
+ })
+ .entry("Print best foot", |v, cx| {
+ cx.dispatch_action(PrintBestFood.boxed_clone())
+ })
})
}
@@ -20,7 +20,6 @@ test-support = [
[dependencies]
db2 = { path = "../db2" }
-call2 = { path = "../call2" }
client2 = { path = "../client2" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
@@ -37,6 +36,7 @@ theme2 = { path = "../theme2" }
util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" }
+async-trait.workspace = true
async-recursion = "1.0.0"
itertools = "0.10"
bincode = "1.2.1"
@@ -8,9 +8,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use theme2::ActiveTheme;
-use ui::{
- h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Label, ListItem, Tooltip,
-};
+use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
pub enum PanelEvent {
ChangePosition,
@@ -719,15 +717,9 @@ impl Render for PanelButtons {
&& panel.position_is_valid(position, cx)
{
let panel = panel.clone();
- menu = menu.entry(
- ListItem::new(
- position.to_label(),
- Label::new(format!("Dock {}", position.to_label())),
- ),
- move |_, cx| {
- panel.set_position(position, cx);
- },
- )
+ menu = menu.entry(position.to_label(), move |_, cx| {
+ panel.set_position(position, cx);
+ })
}
}
menu
@@ -1,6 +1,5 @@
use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result};
-use call2::ActiveCall;
use collections::HashMap;
use db2::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
@@ -127,7 +126,6 @@ impl PaneGroup {
&self,
project: &Model<Project>,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
@@ -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<Project>,
basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
@@ -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<Project>,
basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
@@ -580,7 +574,6 @@ impl PaneAxis {
project,
basis,
follower_states,
- active_call,
active_pane,
zoomed,
app_state,
@@ -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<AppState>, 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<UserStore>,
pub workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs2::Fs>,
+ pub call_factory: CallFactory,
pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
pub node_runtime: Arc<dyn NodeRuntime>,
@@ -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<Workspace>) -> Option<(bool, bool)> {
+ None
+ }
+
+ fn shared_screen_for_peer(
+ &self,
+ peer_id: PeerId,
+ pane: &View<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Box<dyn ItemHandle>> {
+ None
+ }
+
+ fn room_id(&self, cx: &AppContext) -> Option<u64> {
+ None
+ }
+
+ fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>> {
+ anyhow::bail!("TestCallHandler should not be hanging up")
+ }
+
+ fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
+ None
+ }
+}
impl AppState {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> {
@@ -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<Workspace>),
}
+#[async_trait(?Send)]
+pub trait CallHandler {
+ fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
+ fn shared_screen_for_peer(
+ &self,
+ peer_id: PeerId,
+ pane: &View<Pane>,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Box<dyn ItemHandle>>;
+ fn room_id(&self, cx: &AppContext) -> Option<u64>;
+ fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
+ self.room_id(cx).is_some()
+ }
+ fn hang_up(&self, cx: AsyncWindowContext) -> Result<Task<Result<()>>>;
+ fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
+}
+
pub struct Workspace {
window_self: WindowHandle<Self>,
weak_self: WeakView<Self>,
@@ -428,10 +476,10 @@ pub struct Workspace {
titlebar_item: Option<AnyView>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>,
+ call_handler: Box<dyn CallHandler>,
follower_states: HashMap<View<Pane>, FollowerState>,
last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
window_edited: bool,
- active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
app_state: Arc<AppState>,
@@ -459,6 +507,7 @@ struct FollowerState {
enum WorkspaceBounds {}
+type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
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::<Model<ActiveCall>>() {
- let call = cx.global::<Model<ActiveCall>>().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<Self>,
) -> Task<Result<bool>> {
//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<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
- 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<Self>) -> Option<()> {
+ pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> 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<ActiveCall>> {
- self.active_call.as_ref().map(|(call, _)| call)
- }
-
- fn on_active_call_event(
- &mut self,
- _: Model<ActiveCall>,
- event: &call2::room::Event,
- cx: &mut ViewContext<Self>,
- ) {
- 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<u64>,
+ room_id: u64,
update: proto::update_followers::Variant,
cx: &AppContext,
) -> Option<()> {
- if !cx.has_global::<Model<ActiveCall>>() {
- 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| {
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
-version = "0.114.0"
+version = "0.115.0"
publish = false
[lib]
@@ -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<AppState>, 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!()