Merge branch 'main' into panic-hunting

This commit is contained in:
Mikayla 2023-11-22 13:47:17 -08:00
commit 6e84d8fbc0
No known key found for this signature in database
29 changed files with 1343 additions and 1161 deletions

View file

@ -19,16 +19,12 @@ runs:
- name: Limit target directory size - name: Limit target directory size
shell: bash -euxo pipefail {0} 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 - name: Run check
env:
RUSTFLAGS: -D warnings
shell: bash -euxo pipefail {0} shell: bash -euxo pipefail {0}
run: cargo check --tests --workspace run: cargo check --tests --workspace
- name: Run tests - name: Run tests
env:
RUSTFLAGS: -D warnings
shell: bash -euxo pipefail {0} shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast run: cargo nextest run --workspace --no-fail-fast

View file

@ -23,6 +23,9 @@ jobs:
- self-hosted - self-hosted
- test - test
steps: steps:
- name: Set up default .cargo/config.toml
run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
@ -87,7 +90,7 @@ jobs:
submodules: "recursive" submodules: "recursive"
- name: Limit target directory size - 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 - name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }} if: ${{ startsWith(github.ref, 'refs/tags/v') }}

View file

@ -79,7 +79,7 @@ jobs:
submodules: "recursive" submodules: "recursive"
- name: Limit target directory size - 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 - name: Set release channel to nightly
run: | run: |

7
Cargo.lock generated
View file

@ -1186,6 +1186,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-broadcast", "async-broadcast",
"async-trait",
"audio2", "audio2",
"client2", "client2",
"collections", "collections",
@ -1204,6 +1205,7 @@ dependencies = [
"serde_json", "serde_json",
"settings2", "settings2",
"util", "util",
"workspace2",
] ]
[[package]] [[package]]
@ -1664,7 +1666,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.28.0" version = "0.29.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -11381,6 +11383,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-recursion 1.0.5", "async-recursion 1.0.5",
"async-trait",
"bincode", "bincode",
"call2", "call2",
"client2", "client2",
@ -11493,7 +11496,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.114.0" version = "0.115.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"ai", "ai",

View file

@ -31,7 +31,8 @@ media = { path = "../media" }
project = { package = "project2", path = "../project2" } project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" } settings = { package = "settings2", path = "../settings2" }
util = { path = "../util" } util = { path = "../util" }
workspace = {package = "workspace2", path = "../workspace2"}
async-trait.workspace = true
anyhow.workspace = true anyhow.workspace = true
async-broadcast = "0.4" async-broadcast = "0.4"
futures.workspace = true futures.workspace = true

View file

@ -2,24 +2,29 @@ pub mod call_settings;
pub mod participant; pub mod participant;
pub mod room; pub mod room;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
use audio::Audio; use audio::Audio;
use call_settings::CallSettings; 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 collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext,
WeakModel, Subscription, Task, View, ViewContext, WeakModel, WeakView,
}; };
pub use participant::ParticipantLocation;
use postage::watch; use postage::watch;
use project::Project; use project::Project;
use room::Event; use room::Event;
pub use room::Room;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt;
pub use participant::ParticipantLocation; use workspace::{item::ItemHandle, CallHandler, Pane, Workspace};
pub use room::Room;
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
CallSettings::register(cx); 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)] #[cfg(test)]
mod test { mod test {
use gpui::TestAppContext; use gpui::TestAppContext;

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab" default-run = "collab"
edition = "2021" edition = "2021"
name = "collab" name = "collab"
version = "0.28.0" version = "0.29.0"
publish = false publish = false
[[bin]] [[bin]]

View file

@ -221,6 +221,7 @@ impl TestServer {
fs: fs.clone(), fs: fs.clone(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
call_factory: |_, _| Box::new(workspace::TestCallHandler),
}); });
cx.update(|cx| { cx.update(|cx| {

View file

@ -1001,17 +1001,18 @@ impl CompletionsMenu {
fn pre_resolve_completion_documentation( fn pre_resolve_completion_documentation(
&self, &self,
project: Option<ModelHandle<Project>>, editor: &Editor,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) -> Option<Task<()>> {
let settings = settings::get::<EditorSettings>(cx); let settings = settings::get::<EditorSettings>(cx);
if !settings.show_completion_documentation { if !settings.show_completion_documentation {
return; return None;
} }
let Some(project) = project else { let Some(project) = editor.project.clone() else {
return; return None;
}; };
let client = project.read(cx).client(); let client = project.read(cx).client();
let language_registry = project.read(cx).languages().clone(); let language_registry = project.read(cx).languages().clone();
@ -1021,7 +1022,7 @@ impl CompletionsMenu {
let completions = self.completions.clone(); let completions = self.completions.clone();
let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); 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 { if is_remote {
let Some(project_id) = project_id else { let Some(project_id) = project_id else {
log::error!("Remote project without remote_id"); log::error!("Remote project without remote_id");
@ -1083,8 +1084,7 @@ impl CompletionsMenu {
_ = this.update(&mut cx, |_, cx| cx.notify()); _ = this.update(&mut cx, |_, cx| cx.notify());
} }
} }
}) }))
.detach();
} }
fn attempt_resolve_selected_completion_documentation( fn attempt_resolve_selected_completion_documentation(
@ -3580,7 +3580,8 @@ impl Editor {
let id = post_inc(&mut self.next_completion_id); let id = post_inc(&mut self.next_completion_id);
let task = cx.spawn(|this, mut cx| { let task = cx.spawn(|this, mut cx| {
async move { 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 { let mut menu = CompletionsMenu {
id, id,
initial_position: position, initial_position: position,
@ -3601,21 +3602,26 @@ impl Editor {
selected_item: 0, selected_item: 0,
list: Default::default(), list: Default::default(),
}; };
menu.filter(query.as_deref(), cx.background()).await; menu.filter(query.as_deref(), cx.background()).await;
if menu.matches.is_empty() { if menu.matches.is_empty() {
None (None, None)
} else { } else {
_ = this.update(&mut cx, |editor, cx| { let pre_resolve_task = this
menu.pre_resolve_completion_documentation(editor.project.clone(), cx); .update(&mut cx, |editor, cx| {
}); menu.pre_resolve_completion_documentation(editor, cx)
Some(menu) })
.ok()
.flatten();
(Some(menu), pre_resolve_task)
} }
} else { } else {
None (None, None)
}; };
this.update(&mut cx, |this, cx| { 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(); let mut context_menu = this.context_menu.write();
match context_menu.as_ref() { match context_menu.as_ref() {
@ -3636,10 +3642,10 @@ impl Editor {
drop(context_menu); drop(context_menu);
this.discard_copilot_suggestion(cx); this.discard_copilot_suggestion(cx);
cx.notify(); cx.notify();
} else if this.completion_tasks.is_empty() { } else if this.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was // If there are no more completion tasks (omitting ourself) and
// empty, we should hide it. If it was already hidden, we should // the last menu was empty, we should hide it. If it was already
// also show the copilot suggestion when available. // hidden, we should also show the copilot suggestion when available.
drop(context_menu); drop(context_menu);
if this.hide_context_menu(cx).is_none() { if this.hide_context_menu(cx).is_none() {
this.update_visible_copilot_suggestion(cx); 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>(()) Ok::<_, anyhow::Error>(())
} }
.log_err() .log_err()
}); });
self.completion_tasks.push((id, task)); self.completion_tasks.push((id, task));
} }

View file

@ -44,7 +44,7 @@ use gpui::{
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, 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 highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -54,13 +54,13 @@ use itertools::Itertools;
pub use language::{char_kind, CharKind}; pub use language::{char_kind, CharKind};
use language::{ use language::{
language_settings::{self, all_language_settings, InlayHintSettings}, language_settings::{self, all_language_settings, InlayHintSettings},
point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, Completion, CursorShape, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, LanguageRegistry,
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; use lsp::{DiagnosticSeverity, LanguageServerId};
use movement::TextLayoutDetails; use movement::TextLayoutDetails;
use multi_buffer::ToOffsetUtf16; use multi_buffer::ToOffsetUtf16;
pub use multi_buffer::{ pub use multi_buffer::{
@ -97,7 +97,7 @@ use text::{OffsetUtf16, Rope};
use theme::{ use theme::{
ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, 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 util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{ use workspace::{
item::{ItemEvent, ItemHandle}, item::{ItemEvent, ItemHandle},
@ -1224,208 +1224,202 @@ impl CompletionsMenu {
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> AnyElement { ) -> AnyElement {
todo!("old implementation below") let settings = EditorSettings::get_global(cx);
let show_completion_documentation = settings.show_completion_documentation;
let widest_completion_ix = self
.matches
.iter()
.enumerate()
.max_by_key(|(_, mat)| {
let completions = self.completions.read();
let completion = &completions[mat.candidate_id];
let documentation = &completion.documentation;
let mut len = completion.label.text.chars().count();
if let Some(Documentation::SingleLine(text)) = documentation {
if show_completion_documentation {
len += text.chars().count();
}
}
len
})
.map(|(ix, _)| ix);
let completions = self.completions.clone();
let matches = self.matches.clone();
let selected_item = self.selected_item;
let list = uniform_list(
cx.view().clone(),
"completions",
matches.len(),
move |editor, range, cx| {
let start_ix = range.start;
let completions_guard = completions.read();
matches[range]
.iter()
.enumerate()
.map(|(ix, mat)| {
let item_ix = start_ix + ix;
let candidate_id = mat.candidate_id;
let completion = &completions_guard[candidate_id];
let documentation = if show_completion_documentation {
&completion.documentation
} else {
&None
};
// todo!("highlights")
// let highlights = combine_syntax_and_fuzzy_match_highlights(
// &completion.label.text,
// style.text.color.into(),
// styled_runs_for_code_label(&completion.label, &style.syntax),
// &mat.positions,
// )
// todo!("documentation")
// MouseEventHandler::new::<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()
} }
// enum CompletionTag {}
// let settings = EditorSettings>(cx);
// let show_completion_documentation = settings.show_completion_documentation;
// let widest_completion_ix = self
// .matches
// .iter()
// .enumerate()
// .max_by_key(|(_, mat)| {
// let completions = self.completions.read();
// let completion = &completions[mat.candidate_id];
// let documentation = &completion.documentation;
// let mut len = completion.label.text.chars().count();
// if let Some(Documentation::SingleLine(text)) = documentation {
// if show_completion_documentation {
// len += text.chars().count();
// }
// }
// len
// })
// .map(|(ix, _)| ix);
// let completions = self.completions.clone();
// let matches = self.matches.clone();
// let selected_item = self.selected_item;
// let list = UniformList::new(self.list.clone(), matches.len(), cx, {
// let style = style.clone();
// move |_, range, items, cx| {
// let start_ix = range.start;
// let completions_guard = completions.read();
// for (ix, mat) in matches[range].iter().enumerate() {
// let item_ix = start_ix + ix;
// let candidate_id = mat.candidate_id;
// let completion = &completions_guard[candidate_id];
// let documentation = if show_completion_documentation {
// &completion.documentation
// } else {
// &None
// };
// items.push(
// MouseEventHandler::new::<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()
// }
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) { pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
let mut matches = if let Some(query) = query { let mut matches = if let Some(query) = query {
fuzzy::match_strings( fuzzy::match_strings(
@ -1594,6 +1588,7 @@ impl CodeActionsMenu {
.elevation_1(cx) .elevation_1(cx)
.px_2() .px_2()
.py_1() .py_1()
.track_scroll(self.scroll_handle.clone())
.with_width_from_item( .with_width_from_item(
self.actions self.actions
.iter() .iter()
@ -9405,6 +9400,7 @@ impl Render for Editor {
font_style: FontStyle::Normal, font_style: FontStyle::Normal,
line_height: relative(1.).into(), line_height: relative(1.).into(),
underline: None, underline: None,
white_space: WhiteSpace::Normal,
}, },
EditorMode::AutoHeight { max_lines } => todo!(), EditorMode::AutoHeight { max_lines } => todo!(),
@ -9418,6 +9414,7 @@ impl Render for Editor {
font_style: FontStyle::Normal, font_style: FontStyle::Normal,
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),
underline: None, underline: None,
white_space: WhiteSpace::Normal,
}, },
}; };
@ -10126,49 +10123,50 @@ pub fn combine_syntax_and_fuzzy_match_highlights(
result result
} }
// pub fn styled_runs_for_code_label<'a>( pub fn styled_runs_for_code_label<'a>(
// label: &'a CodeLabel, label: &'a CodeLabel,
// syntax_theme: &'a theme::SyntaxTheme, syntax_theme: &'a theme::SyntaxTheme,
// ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> { ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
// let fade_out = HighlightStyle { let fade_out = HighlightStyle {
// fade_out: Some(0.35), fade_out: Some(0.35),
// ..Default::default() ..Default::default()
// }; };
// let mut prev_end = label.filter_range.end; let mut prev_end = label.filter_range.end;
// label label
// .runs .runs
// .iter() .iter()
// .enumerate() .enumerate()
// .flat_map(move |(ix, (range, highlight_id))| { .flat_map(move |(ix, (range, highlight_id))| {
// let style = if let Some(style) = highlight_id.style(syntax_theme) { let style = if let Some(style) = highlight_id.style(syntax_theme) {
// style style
// } else { } else {
// return Default::default(); return Default::default();
// }; };
// let mut muted_style = style; let mut muted_style = style;
// muted_style.highlight(fade_out); muted_style.highlight(fade_out);
// let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new(); let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
// if range.start >= label.filter_range.end { if range.start >= label.filter_range.end {
// if range.start > prev_end { if range.start > prev_end {
// runs.push((prev_end..range.start, fade_out)); runs.push((prev_end..range.start, fade_out));
// } }
// runs.push((range.clone(), muted_style)); runs.push((range.clone(), muted_style));
// } else if range.end <= label.filter_range.end { } else if range.end <= label.filter_range.end {
// runs.push((range.clone(), style)); runs.push((range.clone(), style));
// } else { } else {
// runs.push((range.start..label.filter_range.end, style)); runs.push((range.start..label.filter_range.end, style));
// runs.push((label.filter_range.end..range.end, muted_style)); runs.push((label.filter_range.end..range.end, muted_style));
// } }
// prev_end = cmp::max(prev_end, range.end); prev_end = cmp::max(prev_end, range.end);
// if ix + 1 == label.runs.len() && label.text.len() > prev_end { if ix + 1 == label.runs.len() && label.text.len() > prev_end {
// runs.push((prev_end..label.text.len(), fade_out)); 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 { pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
let mut index = 0; let mut index = 0;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun,
WindowContext, WrappedLine, WhiteSpace, WindowContext, WrappedLine,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
@ -159,10 +159,14 @@ impl TextState {
let element_state = self.clone(); let element_state = self.clone();
move |known_dimensions, available_space| { move |known_dimensions, available_space| {
let wrap_width = known_dimensions.width.or(match available_space.width { let wrap_width = if text_style.white_space == WhiteSpace::Normal {
crate::AvailableSpace::Definite(x) => Some(x), known_dimensions.width.or(match available_space.width {
_ => None, crate::AvailableSpace::Definite(x) => Some(x),
}); _ => None,
})
} else {
None
};
if let Some(text_state) = element_state.0.lock().as_ref() { if let Some(text_state) = element_state.0.lock().as_ref() {
if text_state.size.is_some() if text_state.size.is_some()
@ -174,10 +178,7 @@ impl TextState {
let Some(lines) = text_system let Some(lines) = text_system
.shape_text( .shape_text(
&text, &text, font_size, &runs, wrap_width, // Wrap if we know the width.
font_size,
&runs[..],
wrap_width, // Wrap if we know the width.
) )
.log_err() .log_err()
else { else {
@ -194,7 +195,7 @@ impl TextState {
for line in &lines { for line in &lines {
let line_size = line.size(line_height); let line_size = line.size(line_height);
size.height += line_size.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 { element_state.lock().replace(TextStateInner {

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement, point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Render, RenderOnce, Size, ElementId, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point,
StyleRefinement, Styled, View, ViewContext, WindowContext, Render, RenderOnce, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -22,8 +22,8 @@ where
V: Render, V: Render,
{ {
let id = id.into(); let id = id.into();
let mut style = StyleRefinement::default(); let mut base_style = StyleRefinement::default();
style.overflow.y = Some(Overflow::Hidden); base_style.overflow.y = Some(Overflow::Scroll);
let render_range = move |range, cx: &mut WindowContext| { let render_range = move |range, cx: &mut WindowContext| {
view.update(cx, |this, cx| { view.update(cx, |this, cx| {
@ -36,12 +36,12 @@ where
UniformList { UniformList {
id: id.clone(), id: id.clone(),
style,
item_count, item_count,
item_to_measure_index: 0, item_to_measure_index: 0,
render_items: Box::new(render_range), render_items: Box::new(render_range),
interactivity: Interactivity { interactivity: Interactivity {
element_id: Some(id.into()), element_id: Some(id.into()),
base_style,
..Default::default() ..Default::default()
}, },
scroll_handle: None, scroll_handle: None,
@ -50,7 +50,6 @@ where
pub struct UniformList { pub struct UniformList {
id: ElementId, id: ElementId,
style: StyleRefinement,
item_count: usize, item_count: usize,
item_to_measure_index: usize, item_to_measure_index: usize,
render_items: render_items:
@ -91,7 +90,7 @@ impl UniformListScrollHandle {
impl Styled for UniformList { impl Styled for UniformList {
fn style(&mut self) -> &mut StyleRefinement { 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, 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 = let first_visible_element_ix =
(-scroll_offset.y / item_height).floor() as usize; (-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 let visible_range = first_visible_element_ix
..cmp::min( ..cmp::min(last_visible_element_ix, self.item_count);
first_visible_element_ix + visible_item_count,
self.item_count,
);
let items = (self.render_items)(visible_range.clone(), cx); let items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| { cx.with_z_index(1, |cx| {
for (item, ix) in items.into_iter().zip(visible_range) { let content_mask = ContentMask {
let item_origin = padded_bounds.origin bounds: padded_bounds,
+ point(px(0.), item_height * ix + scroll_offset.y); };
let available_space = size( cx.with_content_mask(Some(content_mask), |cx| {
AvailableSpace::Definite(padded_bounds.size.width), for (item, ix) in items.into_iter().zip(visible_range) {
AvailableSpace::Definite(item_height), let item_origin = padded_bounds.origin
); + point(px(0.), item_height * ix + scroll_offset.y);
item.draw(item_origin, available_space, cx); let available_space = size(
} AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, cx);
}
});
}); });
} }
}) })

View file

@ -128,6 +128,13 @@ pub struct BoxShadow {
pub spread_radius: Pixels, pub spread_radius: Pixels,
} }
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum WhiteSpace {
#[default]
Normal,
Nowrap,
}
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(Debug)] #[refineable(Debug)]
pub struct TextStyle { pub struct TextStyle {
@ -139,6 +146,7 @@ pub struct TextStyle {
pub font_weight: FontWeight, pub font_weight: FontWeight,
pub font_style: FontStyle, pub font_style: FontStyle,
pub underline: Option<UnderlineStyle>, pub underline: Option<UnderlineStyle>,
pub white_space: WhiteSpace,
} }
impl Default for TextStyle { impl Default for TextStyle {
@ -152,6 +160,7 @@ impl Default for TextStyle {
font_weight: FontWeight::default(), font_weight: FontWeight::default(),
font_style: FontStyle::default(), font_style: FontStyle::default(),
underline: None, underline: None,
white_space: WhiteSpace::Normal,
} }
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
SharedString, StyleRefinement, Visibility, SharedString, StyleRefinement, Visibility, WhiteSpace,
}; };
use crate::{BoxShadow, TextStyleRefinement}; use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@ -101,6 +101,24 @@ pub trait Styled: Sized {
self 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`. /// Sets the flex direction of the element to `column`.
/// [Docs](https://tailwindcss.com/docs/flex-direction#column) /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
fn flex_col(mut self) -> Self { fn flex_col(mut self) -> Self {

View file

@ -7,6 +7,7 @@ pub use crate::{
use crate::{ use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
language_settings::{language_settings, LanguageSettings}, language_settings::{language_settings, LanguageSettings},
markdown::parse_markdown,
outline::OutlineItem, outline::OutlineItem,
syntax_map::{ syntax_map::{
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
@ -155,12 +156,52 @@ pub struct Diagnostic {
pub is_unnecessary: bool, 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)] #[derive(Clone, Debug)]
pub struct Completion { pub struct Completion {
pub old_range: Range<Anchor>, pub old_range: Range<Anchor>,
pub new_text: String, pub new_text: String,
pub label: CodeLabel, pub label: CodeLabel,
pub server_id: LanguageServerId, pub server_id: LanguageServerId,
pub documentation: Option<Documentation>,
pub lsp_completion: lsp::CompletionItem, pub lsp_completion: lsp::CompletionItem,
} }

View file

@ -482,6 +482,7 @@ pub async fn deserialize_completion(
lsp_completion.filter_text.as_deref(), lsp_completion.filter_text.as_deref(),
) )
}), }),
documentation: None,
server_id: LanguageServerId(completion.server_id as usize), server_id: LanguageServerId(completion.server_id as usize),
lsp_completion, lsp_completion,
}) })

View file

@ -10,7 +10,7 @@ use futures::future;
use gpui::{AppContext, AsyncAppContext, Model}; use gpui::{AppContext, AsyncAppContext, Model};
use language::{ use language::{
language_settings::{language_settings, InlayHintKind}, 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}, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
@ -1339,7 +1339,7 @@ impl LspCommand for GetCompletions {
async fn response_from_lsp( async fn response_from_lsp(
self, self,
completions: Option<lsp::CompletionResponse>, completions: Option<lsp::CompletionResponse>,
_: Model<Project>, project: Model<Project>,
buffer: Model<Buffer>, buffer: Model<Buffer>,
server_id: LanguageServerId, server_id: LanguageServerId,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
@ -1359,7 +1359,8 @@ impl LspCommand for GetCompletions {
Default::default() 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 language = buffer.language().cloned();
let snapshot = buffer.snapshot(); let snapshot = buffer.snapshot();
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); 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(); let language = language.clone();
LineEnding::normalize(&mut new_text); LineEnding::normalize(&mut new_text);
Some(async move { Some(async move {
let mut label = None; let mut label = None;
if let Some(language) = language { if let Some(language) = language.as_ref() {
language.process_completion(&mut lsp_completion).await; language.process_completion(&mut lsp_completion).await;
label = language.label_for_completion(&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 { Completion {
old_range, old_range,
new_text, new_text,
@ -1460,6 +1476,7 @@ impl LspCommand for GetCompletions {
lsp_completion.filter_text.as_deref(), lsp_completion.filter_text.as_deref(),
) )
}), }),
documentation,
server_id, server_id,
lsp_completion, lsp_completion,
} }

View file

@ -371,7 +371,7 @@ impl ProjectPanel {
_entry_id: ProjectEntryId, _entry_id: ProjectEntryId,
_cx: &mut ViewContext<Self>, _cx: &mut ViewContext<Self>,
) { ) {
todo!() // todo!()
// let project = self.project.read(cx); // let project = self.project.read(cx);
// let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) { // let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {

View file

@ -31,7 +31,7 @@ use workspace::{
notifications::NotifyResultExt, notifications::NotifyResultExt,
register_deserializable_item, register_deserializable_item,
searchable::{SearchEvent, SearchOptions, SearchableItem}, searchable::{SearchEvent, SearchOptions, SearchableItem},
ui::{ContextMenu, Icon, IconElement, Label, ListItem}, ui::{ContextMenu, Icon, IconElement, Label},
CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId,
}; };
@ -299,11 +299,8 @@ impl TerminalView {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.context_menu = Some(ContextMenu::build(cx, |menu, _| { self.context_menu = Some(ContextMenu::build(cx, |menu, _| {
menu.action(ListItem::new("clear", Label::new("Clear")), Box::new(Clear)) menu.action("Clear", Box::new(Clear))
.action( .action("Close", Box::new(CloseActiveItem { save_intent: None }))
ListItem::new("close", Label::new("Close")),
Box::new(CloseActiveItem { save_intent: None }),
)
})); }));
dbg!(&position); dbg!(&position);
// todo!() // todo!()

View file

@ -1,7 +1,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use crate::{prelude::*, v_stack, List}; use crate::{prelude::*, v_stack, Label, List};
use crate::{ListItem, ListSeparator, ListSubHeader}; use crate::{ListItem, ListSeparator, ListSubHeader};
use gpui::{ use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase, overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, ClickEvent, DispatchPhase,
@ -10,9 +10,9 @@ use gpui::{
}; };
pub enum ContextMenuItem { pub enum ContextMenuItem {
Separator(ListSeparator), Separator,
Header(ListSubHeader), Header(SharedString),
Entry(ListItem, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>), Entry(SharedString, Rc<dyn Fn(&ClickEvent, &mut WindowContext)>),
} }
pub struct ContextMenu { pub struct ContextMenu {
@ -46,29 +46,30 @@ impl ContextMenu {
} }
pub fn header(mut self, title: impl Into<SharedString>) -> Self { pub fn header(mut self, title: impl Into<SharedString>) -> Self {
self.items self.items.push(ContextMenuItem::Header(title.into()));
.push(ContextMenuItem::Header(ListSubHeader::new(title)));
self self
} }
pub fn separator(mut self) -> Self { pub fn separator(mut self) -> Self {
self.items.push(ContextMenuItem::Separator(ListSeparator)); self.items.push(ContextMenuItem::Separator);
self self
} }
pub fn entry( pub fn entry(
mut self, mut self,
view: ListItem, label: impl Into<SharedString>,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> Self { ) -> Self {
self.items self.items
.push(ContextMenuItem::Entry(view, Rc::new(on_click))); .push(ContextMenuItem::Entry(label.into(), Rc::new(on_click)));
self 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 // 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>) { 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) // .border_color(cx.theme().colors().border)
.child( .child(
List::new().children(self.items.iter().map(|item| match item { List::new().children(self.items.iter().map(|item| match item {
ContextMenuItem::Separator(separator) => { ContextMenuItem::Separator => ListSeparator::new().render_into_any(),
separator.clone().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) => { ContextMenuItem::Entry(entry, callback) => {
let callback = callback.clone(); let callback = callback.clone();
let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss)); let dismiss = cx.listener(|_, _, cx| cx.emit(Manager::Dismiss));
entry ListItem::new(entry.clone())
.clone() .child(Label::new(entry.clone()))
.on_click(move |event, cx| { .on_click(move |event, cx| {
callback(event, cx); callback(event, cx);
dismiss(event, cx) dismiss(event, cx)

View file

@ -245,45 +245,28 @@ pub struct ListItem {
// TODO: Reintroduce this // TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility, // disclosure_control_style: DisclosureControlVisibility,
indent_level: u32, indent_level: u32,
label: Label,
left_slot: Option<GraphicSlot>, left_slot: Option<GraphicSlot>,
overflow: OverflowStyle, overflow: OverflowStyle,
size: ListEntrySize, size: ListEntrySize,
toggle: Toggle, toggle: Toggle,
variant: ListItemVariant, variant: ListItemVariant,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>, on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
} children: SmallVec<[AnyElement; 2]>,
impl Clone for ListItem {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
disabled: self.disabled,
indent_level: self.indent_level,
label: self.label.clone(),
left_slot: self.left_slot.clone(),
overflow: self.overflow,
size: self.size,
toggle: self.toggle,
variant: self.variant,
on_click: self.on_click.clone(),
}
}
} }
impl ListItem { impl ListItem {
pub fn new(id: impl Into<ElementId>, label: Label) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(), id: id.into(),
disabled: false, disabled: false,
indent_level: 0, indent_level: 0,
label,
left_slot: None, left_slot: None,
overflow: OverflowStyle::Hidden, overflow: OverflowStyle::Hidden,
size: ListEntrySize::default(), size: ListEntrySize::default(),
toggle: Toggle::NotToggleable, toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(), variant: ListItemVariant::default(),
on_click: Default::default(), on_click: Default::default(),
children: SmallVec::new(),
} }
} }
@ -394,11 +377,17 @@ impl Component for ListItem {
.relative() .relative()
.child(disclosure_control(self.toggle)) .child(disclosure_control(self.toggle))
.children(left_content) .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)] #[derive(RenderOnce, Clone)]
pub struct ListSeparator; pub struct ListSeparator;

View file

@ -2,7 +2,7 @@ use gpui::{actions, Action, AnchorCorner, Div, Render, View};
use story::Story; use story::Story;
use crate::prelude::*; use crate::prelude::*;
use crate::{menu_handle, ContextMenu, Label, ListItem}; use crate::{menu_handle, ContextMenu, Label};
actions!(PrintCurrentDate, PrintBestFood); actions!(PrintCurrentDate, PrintBestFood);
@ -10,17 +10,13 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
ContextMenu::build(cx, |menu, _| { ContextMenu::build(cx, |menu, _| {
menu.header(header) menu.header(header)
.separator() .separator()
.entry( .entry("Print current time", |v, cx| {
ListItem::new("Print current time", Label::new("Print current time")), println!("dispatching PrintCurrentTime action");
|v, cx| { cx.dispatch_action(PrintCurrentDate.boxed_clone())
println!("dispatching PrintCurrentTime action"); })
cx.dispatch_action(PrintCurrentDate.boxed_clone()) .entry("Print best foot", |v, cx| {
}, cx.dispatch_action(PrintBestFood.boxed_clone())
) })
.entry(
ListItem::new("Print best food", Label::new("Print best food")),
|v, cx| cx.dispatch_action(PrintBestFood.boxed_clone()),
)
}) })
} }

View file

@ -20,7 +20,6 @@ test-support = [
[dependencies] [dependencies]
db2 = { path = "../db2" } db2 = { path = "../db2" }
call2 = { path = "../call2" }
client2 = { path = "../client2" } client2 = { path = "../client2" }
collections = { path = "../collections" } collections = { path = "../collections" }
# context_menu = { path = "../context_menu" } # context_menu = { path = "../context_menu" }
@ -37,6 +36,7 @@ theme2 = { path = "../theme2" }
util = { path = "../util" } util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" } ui = { package = "ui2", path = "../ui2" }
async-trait.workspace = true
async-recursion = "1.0.0" async-recursion = "1.0.0"
itertools = "0.10" itertools = "0.10"
bincode = "1.2.1" bincode = "1.2.1"

View file

@ -8,9 +8,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use theme2::ActiveTheme; use theme2::ActiveTheme;
use ui::{ use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Label, ListItem, Tooltip,
};
pub enum PanelEvent { pub enum PanelEvent {
ChangePosition, ChangePosition,
@ -719,15 +717,9 @@ impl Render for PanelButtons {
&& panel.position_is_valid(position, cx) && panel.position_is_valid(position, cx)
{ {
let panel = panel.clone(); let panel = panel.clone();
menu = menu.entry( menu = menu.entry(position.to_label(), move |_, cx| {
ListItem::new( panel.set_position(position, cx);
position.to_label(), })
Label::new(format!("Dock {}", position.to_label())),
),
move |_, cx| {
panel.set_position(position, cx);
},
)
} }
} }
menu menu

View file

@ -1,6 +1,5 @@
use crate::{AppState, FollowerState, Pane, Workspace}; use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use call2::ActiveCall;
use collections::HashMap; use collections::HashMap;
use db2::sqlez::{ use db2::sqlez::{
bindable::{Bind, Column, StaticColumnCount}, bindable::{Bind, Column, StaticColumnCount},
@ -127,7 +126,6 @@ impl PaneGroup {
&self, &self,
project: &Model<Project>, project: &Model<Project>,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
@ -137,7 +135,6 @@ impl PaneGroup {
project, project,
0, 0,
follower_states, follower_states,
active_call,
active_pane, active_pane,
zoomed, zoomed,
app_state, app_state,
@ -199,7 +196,6 @@ impl Member {
project: &Model<Project>, project: &Model<Project>,
basis: usize, basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
@ -235,7 +231,6 @@ impl Member {
project, project,
basis + 1, basis + 1,
follower_states, follower_states,
active_call,
active_pane, active_pane,
zoomed, zoomed,
app_state, app_state,
@ -558,7 +553,6 @@ impl PaneAxis {
project: &Model<Project>, project: &Model<Project>,
basis: usize, basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
@ -580,7 +574,6 @@ impl PaneAxis {
project, project,
basis, basis,
follower_states, follower_states,
active_call,
active_pane, active_pane,
zoomed, zoomed,
app_state, app_state,

View file

@ -16,7 +16,7 @@ mod toolbar;
mod workspace_settings; mod workspace_settings;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use call2::ActiveCall; use async_trait::async_trait;
use client2::{ use client2::{
proto::{self, PeerId}, proto::{self, PeerId},
Client, TypedEnvelope, UserStore, Client, TypedEnvelope, UserStore,
@ -33,8 +33,8 @@ use gpui::{
AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext,
WindowOptions, WindowHandle, WindowOptions,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools; use itertools::Itertools;
@ -210,7 +210,6 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
init_settings(cx); init_settings(cx);
notifications::init(cx); notifications::init(cx);
// cx.add_global_action({ // cx.add_global_action({
// let app_state = Arc::downgrade(&app_state); // let app_state = Arc::downgrade(&app_state);
// move |_: &Open, cx: &mut AppContext| { // move |_: &Open, cx: &mut AppContext| {
@ -304,6 +303,7 @@ pub struct AppState {
pub user_store: Model<UserStore>, pub user_store: Model<UserStore>,
pub workspace_store: Model<WorkspaceStore>, pub workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs2::Fs>, pub fs: Arc<dyn fs2::Fs>,
pub call_factory: CallFactory,
pub build_window_options: pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions, fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
pub node_runtime: Arc<dyn NodeRuntime>, pub node_runtime: Arc<dyn NodeRuntime>,
@ -322,6 +322,36 @@ struct Follower {
peer_id: PeerId, 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 { impl AppState {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> { pub fn test(cx: &mut AppContext) -> Arc<Self> {
@ -352,6 +382,7 @@ impl AppState {
workspace_store, workspace_store,
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
call_factory: |_, _| Box::new(TestCallHandler),
}) })
} }
} }
@ -408,6 +439,23 @@ pub enum Event {
WorkspaceCreated(WeakView<Workspace>), 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 { pub struct Workspace {
window_self: WindowHandle<Self>, window_self: WindowHandle<Self>,
weak_self: WeakView<Self>, weak_self: WeakView<Self>,
@ -428,10 +476,10 @@ pub struct Workspace {
titlebar_item: Option<AnyView>, titlebar_item: Option<AnyView>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>, notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>, project: Model<Project>,
call_handler: Box<dyn CallHandler>,
follower_states: HashMap<View<Pane>, FollowerState>, follower_states: HashMap<View<Pane>, FollowerState>,
last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>, last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
window_edited: bool, window_edited: bool,
active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId, database_id: WorkspaceId,
app_state: Arc<AppState>, app_state: Arc<AppState>,
@ -459,6 +507,7 @@ struct FollowerState {
enum WorkspaceBounds {} enum WorkspaceBounds {}
type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
impl Workspace { impl Workspace {
pub fn new( pub fn new(
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
@ -550,9 +599,19 @@ impl Workspace {
mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
let _apply_leader_updates = cx.spawn(|this, mut cx| async move { let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
while let Some((leader_id, update)) = leader_updates_rx.next().await { 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 .await
.log_err(); .log_err();
// this.update(&mut cx, |this, cxx| {
// this.call_handler
// .process_leader_update(leader_id, update, cx2)
// })?
// .await
// .log_err();
} }
Ok(()) Ok(())
@ -585,14 +644,6 @@ impl Workspace {
// drag_and_drop.register_container(weak_handle.clone()); // 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![ let subscriptions = vec![
cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_activation(Self::on_window_activation_changed),
cx.observe_window_bounds(move |_, cx| { cx.observe_window_bounds(move |_, cx| {
@ -655,7 +706,8 @@ impl Workspace {
follower_states: Default::default(), follower_states: Default::default(),
last_leaders_by_pane: Default::default(), last_leaders_by_pane: Default::default(),
window_edited: false, window_edited: false,
active_call,
call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
database_id: workspace_id, database_id: workspace_id,
app_state, app_state,
_observe_current_user, _observe_current_user,
@ -1102,7 +1154,7 @@ impl Workspace {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
//todo!(saveing) //todo!(saveing)
let active_call = self.active_call().cloned();
let window = cx.window_handle(); let window = cx.window_handle();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -1113,27 +1165,27 @@ impl Workspace {
.count() .count()
})?; })?;
if let Some(active_call) = active_call { if !quitting
if !quitting && workspace_count == 1
&& workspace_count == 1 && this
&& active_call.read_with(&cx, |call, _| call.room().is_some())? .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx))
{ .log_err()
let answer = window.update(&mut cx, |_, cx| { .unwrap_or_default()
cx.prompt( {
PromptLevel::Warning, let answer = window.update(&mut cx, |_, cx| {
"Do you want to leave the current call?", cx.prompt(
&["Close window and hang up", "Cancel"], PromptLevel::Warning,
) "Do you want to leave the current call?",
})?; &["Close window and hang up", "Cancel"],
)
})?;
if answer.await.log_err() == Some(1) { if answer.await.log_err() == Some(1) {
return anyhow::Ok(false); return anyhow::Ok(false);
} else { } else {
active_call this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx.to_async()))??
.update(&mut cx, |call, cx| call.hang_up(cx))? .await
.await .log_err();
.log_err();
}
} }
} }
@ -2391,19 +2443,19 @@ impl Workspace {
// } // }
pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> { 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; let leader_id = state.leader_id;
for (_, item) in state.items_by_leader_view_id { for (_, item) in state.items_by_leader_view_id {
item.set_leader_peer_id(None, cx); item.set_leader_peer_id(None, cx);
} }
if self if follower_states
.follower_states
.values() .values()
.all(|state| state.leader_id != state.leader_id) .all(|state| state.leader_id != state.leader_id)
{ {
let project_id = self.project.read(cx).remote_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 self.app_state
.client .client
.send(proto::Unfollow { .send(proto::Unfollow {
@ -2762,8 +2814,9 @@ impl Workspace {
} else { } else {
None None
}; };
let room_id = self.call_handler.room_id(cx)?;
self.app_state().workspace_store.update(cx, |store, 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) 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(); cx.notify();
let call = self.active_call()?; let (leader_in_this_project, leader_in_this_app) =
let room = call.read(cx).room()?.read(cx); self.call_handler.peer_state(leader_id, cx)?;
let participant = room.remote_participant_for_peer_id(leader_id)?;
let mut items_to_activate = Vec::new(); 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 { for (pane, state) in &self.follower_states {
if state.leader_id != leader_id { if state.leader_id != leader_id {
continue; continue;
@ -2825,8 +2859,8 @@ impl Workspace {
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { 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)); pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
} else { } else {
pane.update(cx, |pane, cx| { pane.update(cx, |pane, mut cx| {
pane.add_item(item.boxed_clone(), false, false, None, 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 { pub fn database_id(&self) -> WorkspaceId {
self.database_id self.database_id
} }
@ -3314,6 +3329,7 @@ impl Workspace {
fs: project.read(cx).fs().clone(), fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
call_factory: |_, _| Box::new(TestCallHandler),
}); });
let workspace = Self::new(0, project, app_state, cx); let workspace = Self::new(0, project, app_state, cx);
workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@ -3672,7 +3688,6 @@ impl Render for Workspace {
.child(self.center.render( .child(self.center.render(
&self.project, &self.project,
&self.follower_states, &self.follower_states,
self.active_call(),
&self.active_pane, &self.active_pane,
self.zoomed.as_ref(), self.zoomed.as_ref(),
&self.app_state, &self.app_state,
@ -3842,14 +3857,10 @@ impl WorkspaceStore {
pub fn update_followers( pub fn update_followers(
&self, &self,
project_id: Option<u64>, project_id: Option<u64>,
room_id: u64,
update: proto::update_followers::Variant, update: proto::update_followers::Variant,
cx: &AppContext, cx: &AppContext,
) -> Option<()> { ) -> 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 let follower_ids: Vec<_> = self
.followers .followers
.iter() .iter()
@ -3885,9 +3896,17 @@ impl WorkspaceStore {
project_id: envelope.payload.project_id, project_id: envelope.payload.project_id,
peer_id: envelope.original_sender_id()?, peer_id: envelope.original_sender_id()?,
}; };
let active_project = ActiveCall::global(cx).read(cx).location().cloned();
let mut response = proto::FollowResponse::default(); 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 { for workspace in &this.workspaces {
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor." description = "The fast, collaborative code editor."
edition = "2021" edition = "2021"
name = "zed" name = "zed"
version = "0.114.0" version = "0.115.0"
publish = false publish = false
[lib] [lib]

View file

@ -180,6 +180,7 @@ fn main() {
user_store, user_store,
fs, fs,
build_window_options, build_window_options,
call_factory: call::Call::new,
// background_actions: todo!("ask Mikayla"), // background_actions: todo!("ask Mikayla"),
workspace_store, workspace_store,
node_runtime, 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))? cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))?
.await .await
.log_err(); .log_err();
// todo!(welcome) // todo!(welcome)
//} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
//todo!() //todo!()