Merge branch 'main' into rename-element-traits
# Conflicts: # crates/gpui2/src/elements/uniform_list.rs # crates/ui2/src/components/context_menu.rs # crates/ui2/src/components/list.rs
This commit is contained in:
commit
3b918bfee8
34 changed files with 592 additions and 326 deletions
12
.cargo/ci-config.toml
Normal file
12
.cargo/ci-config.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
|
||||||
|
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
|
||||||
|
# with preference for settings from Zeds config.toml.
|
||||||
|
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
|
||||||
|
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
|
||||||
|
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
|
||||||
|
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
|
||||||
|
# would be incovenient.
|
||||||
|
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
|
||||||
|
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
|
||||||
|
[build]
|
||||||
|
rustflags = ["-D", "warnings"]
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -23,15 +23,15 @@ 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:
|
||||||
clean: false
|
clean: false
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: Set up default .cargo/config.toml
|
||||||
|
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
|
||||||
|
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
uses: ./.github/actions/check_formatting
|
uses: ./.github/actions/check_formatting
|
||||||
|
|
||||||
|
|
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -464,7 +469,7 @@ impl ActiveCall {
|
||||||
&self.pending_invites
|
&self.pending_invites
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
|
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
|
||||||
if let Some(room) = self.room() {
|
if let Some(room) = self.room() {
|
||||||
let room = room.read(cx);
|
let room = room.read(cx);
|
||||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
|
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
|
||||||
|
@ -477,7 +482,7 @@ pub fn report_call_event_for_room(
|
||||||
room_id: u64,
|
room_id: u64,
|
||||||
channel_id: Option<u64>,
|
channel_id: Option<u64>,
|
||||||
client: &Arc<Client>,
|
client: &Arc<Client>,
|
||||||
cx: &AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let telemetry = client.telemetry();
|
let telemetry = client.telemetry();
|
||||||
let telemetry_settings = *TelemetrySettings::get_global(cx);
|
let telemetry_settings = *TelemetrySettings::get_global(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;
|
||||||
|
|
|
@ -109,6 +109,10 @@ pub enum ClickhouseEvent {
|
||||||
virtual_memory_in_bytes: u64,
|
virtual_memory_in_bytes: u64,
|
||||||
milliseconds_since_first_event: i64,
|
milliseconds_since_first_event: i64,
|
||||||
},
|
},
|
||||||
|
App {
|
||||||
|
operation: &'static str,
|
||||||
|
milliseconds_since_first_event: i64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -168,13 +172,8 @@ impl Telemetry {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
state.session_id = Some(session_id.into());
|
state.session_id = Some(session_id.into());
|
||||||
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
if has_clickhouse_events {
|
|
||||||
self.flush_clickhouse_events();
|
|
||||||
}
|
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
||||||
|
@ -256,7 +255,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_copilot_event(
|
pub fn report_copilot_event(
|
||||||
|
@ -273,7 +272,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_assistant_event(
|
pub fn report_assistant_event(
|
||||||
|
@ -290,7 +289,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_call_event(
|
pub fn report_call_event(
|
||||||
|
@ -307,7 +306,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_cpu_event(
|
pub fn report_cpu_event(
|
||||||
|
@ -322,7 +321,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_memory_event(
|
pub fn report_memory_event(
|
||||||
|
@ -337,7 +336,21 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// app_events are called at app open and app close, so flush is set to immediately send
|
||||||
|
pub fn report_app_event(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
telemetry_settings: TelemetrySettings,
|
||||||
|
operation: &'static str,
|
||||||
|
) {
|
||||||
|
let event = ClickhouseEvent::App {
|
||||||
|
operation,
|
||||||
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.report_clickhouse_event(event, telemetry_settings, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn milliseconds_since_first_event(&self) -> i64 {
|
fn milliseconds_since_first_event(&self) -> i64 {
|
||||||
|
@ -358,6 +371,7 @@ impl Telemetry {
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
event: ClickhouseEvent,
|
event: ClickhouseEvent,
|
||||||
telemetry_settings: TelemetrySettings,
|
telemetry_settings: TelemetrySettings,
|
||||||
|
immediate_flush: bool,
|
||||||
) {
|
) {
|
||||||
if !telemetry_settings.metrics {
|
if !telemetry_settings.metrics {
|
||||||
return;
|
return;
|
||||||
|
@ -370,7 +384,7 @@ impl Telemetry {
|
||||||
.push(ClickhouseEventWrapper { signed_in, event });
|
.push(ClickhouseEventWrapper { signed_in, event });
|
||||||
|
|
||||||
if state.installation_id.is_some() {
|
if state.installation_id.is_some() {
|
||||||
if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
||||||
drop(state);
|
drop(state);
|
||||||
self.flush_clickhouse_events();
|
self.flush_clickhouse_events();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -382,7 +382,7 @@ impl settings::Settings for TelemetrySettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
id: AtomicU64::new(0),
|
id: AtomicU64::new(0),
|
||||||
peer: Peer::new(0),
|
peer: Peer::new(0),
|
||||||
|
|
|
@ -107,6 +107,10 @@ pub enum ClickhouseEvent {
|
||||||
virtual_memory_in_bytes: u64,
|
virtual_memory_in_bytes: u64,
|
||||||
milliseconds_since_first_event: i64,
|
milliseconds_since_first_event: i64,
|
||||||
},
|
},
|
||||||
|
App {
|
||||||
|
operation: &'static str,
|
||||||
|
milliseconds_since_first_event: i64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -122,12 +126,13 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
|
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
impl Telemetry {
|
impl Telemetry {
|
||||||
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||||
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
||||||
Some(cx.global::<ReleaseChannel>().display_name())
|
Some(cx.global::<ReleaseChannel>().display_name())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||||
let this = Arc::new(Self {
|
let this = Arc::new(Self {
|
||||||
http_client: client,
|
http_client: client,
|
||||||
|
@ -147,9 +152,22 @@ impl Telemetry {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive
|
||||||
|
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send
|
||||||
|
// std::mem::forget(cx.on_app_quit({
|
||||||
|
// let this = this.clone();
|
||||||
|
// move |cx| this.shutdown_telemetry(cx)
|
||||||
|
// }));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
|
||||||
|
// let telemetry_settings = TelemetrySettings::get_global(cx).clone();
|
||||||
|
// self.report_app_event(telemetry_settings, "close");
|
||||||
|
// Task::ready(())
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
pub fn log_file_path(&self) -> Option<PathBuf> {
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||||
}
|
}
|
||||||
|
@ -163,13 +181,8 @@ impl Telemetry {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
state.session_id = Some(session_id.into());
|
state.session_id = Some(session_id.into());
|
||||||
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
|
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
if has_clickhouse_events {
|
|
||||||
self.flush_clickhouse_events();
|
|
||||||
}
|
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
||||||
|
@ -257,7 +270,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_copilot_event(
|
pub fn report_copilot_event(
|
||||||
|
@ -274,7 +287,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_assistant_event(
|
pub fn report_assistant_event(
|
||||||
|
@ -291,7 +304,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_call_event(
|
pub fn report_call_event(
|
||||||
|
@ -308,7 +321,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_cpu_event(
|
pub fn report_cpu_event(
|
||||||
|
@ -323,7 +336,7 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_memory_event(
|
pub fn report_memory_event(
|
||||||
|
@ -338,7 +351,21 @@ impl Telemetry {
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings)
|
self.report_clickhouse_event(event, telemetry_settings, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// app_events are called at app open and app close, so flush is set to immediately send
|
||||||
|
pub fn report_app_event(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
telemetry_settings: TelemetrySettings,
|
||||||
|
operation: &'static str,
|
||||||
|
) {
|
||||||
|
let event = ClickhouseEvent::App {
|
||||||
|
operation,
|
||||||
|
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.report_clickhouse_event(event, telemetry_settings, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn milliseconds_since_first_event(&self) -> i64 {
|
fn milliseconds_since_first_event(&self) -> i64 {
|
||||||
|
@ -359,6 +386,7 @@ impl Telemetry {
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
event: ClickhouseEvent,
|
event: ClickhouseEvent,
|
||||||
telemetry_settings: TelemetrySettings,
|
telemetry_settings: TelemetrySettings,
|
||||||
|
immediate_flush: bool,
|
||||||
) {
|
) {
|
||||||
if !telemetry_settings.metrics {
|
if !telemetry_settings.metrics {
|
||||||
return;
|
return;
|
||||||
|
@ -371,7 +399,7 @@ impl Telemetry {
|
||||||
.push(ClickhouseEventWrapper { signed_in, event });
|
.push(ClickhouseEventWrapper { signed_in, event });
|
||||||
|
|
||||||
if state.installation_id.is_some() {
|
if state.installation_id.is_some() {
|
||||||
if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
||||||
drop(state);
|
drop(state);
|
||||||
self.flush_clickhouse_events();
|
self.flush_clickhouse_events();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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]]
|
||||||
|
|
|
@ -149,7 +149,7 @@ impl TestServer {
|
||||||
.user_id
|
.user_id
|
||||||
};
|
};
|
||||||
let client_name = name.to_string();
|
let client_name = name.to_string();
|
||||||
let mut client = cx.read(|cx| Client::new(http.clone(), cx));
|
let mut client = cx.update(|cx| Client::new(http.clone(), cx));
|
||||||
let server = self.server.clone();
|
let server = self.server.clone();
|
||||||
let db = self.app_state.db.clone();
|
let db = self.app_state.db.clone();
|
||||||
let connection_killers = self.connection_killers.clone();
|
let connection_killers = self.connection_killers.clone();
|
||||||
|
@ -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| {
|
||||||
|
|
|
@ -1550,7 +1550,7 @@ mod tests {
|
||||||
block_id: ix,
|
block_id: ix,
|
||||||
editor_style: &editor::EditorStyle::default(),
|
editor_style: &editor::EditorStyle::default(),
|
||||||
})
|
})
|
||||||
.element_id()?
|
.inner_id()?
|
||||||
.try_into()
|
.try_into()
|
||||||
.ok()?,
|
.ok()?,
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl Render for DiagnosticIndicator {
|
||||||
};
|
};
|
||||||
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.id(cx.entity_id())
|
.id("diagnostic-indicator")
|
||||||
.on_action(cx.listener(Self::go_to_next_diagnostic))
|
.on_action(cx.listener(Self::go_to_next_diagnostic))
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -966,20 +966,22 @@ impl CompletionsMenu {
|
||||||
|
|
||||||
fn pre_resolve_completion_documentation(
|
fn pre_resolve_completion_documentation(
|
||||||
&self,
|
&self,
|
||||||
project: Option<Model<Project>>,
|
_editor: &Editor,
|
||||||
cx: &mut ViewContext<Editor>,
|
_cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) -> Option<Task<()>> {
|
||||||
// todo!("implementation below ");
|
// todo!("implementation below ");
|
||||||
|
None
|
||||||
}
|
}
|
||||||
// ) {
|
// {
|
||||||
// let settings = EditorSettings::get_global(cx);
|
// let settings = EditorSettings::get_global(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();
|
||||||
|
|
||||||
|
@ -989,7 +991,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");
|
||||||
|
@ -1051,8 +1053,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(
|
||||||
|
@ -3596,7 +3597,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,
|
||||||
|
@ -3619,20 +3621,24 @@ impl Editor {
|
||||||
};
|
};
|
||||||
menu.filter(query.as_deref(), cx.background_executor().clone())
|
menu.filter(query.as_deref(), cx.background_executor().clone())
|
||||||
.await;
|
.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() {
|
||||||
|
@ -3664,10 +3670,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1051,7 +1051,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if list_origin.y + list_height > text_bounds.lower_right().y {
|
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);
|
context_menu.draw(list_origin, available_space, cx);
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub use entity_map::*;
|
||||||
pub use model_context::*;
|
pub use model_context::*;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use smol::future::FutureExt;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test_context::*;
|
pub use test_context::*;
|
||||||
|
|
||||||
|
@ -983,6 +984,22 @@ impl AppContext {
|
||||||
pub fn all_action_names(&self) -> &[SharedString] {
|
pub fn all_action_names(&self) -> &[SharedString] {
|
||||||
self.actions.all_action_names()
|
self.actions.all_action_names()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_app_quit<Fut>(
|
||||||
|
&mut self,
|
||||||
|
mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
|
||||||
|
) -> Subscription
|
||||||
|
where
|
||||||
|
Fut: 'static + Future<Output = ()>,
|
||||||
|
{
|
||||||
|
self.quit_observers.insert(
|
||||||
|
(),
|
||||||
|
Box::new(move |cx| {
|
||||||
|
let future = on_quit(cx);
|
||||||
|
async move { future.await }.boxed_local()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AppContext {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -429,10 +429,6 @@ impl AnyElement {
|
||||||
AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
|
AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn element_id(&self) -> Option<ElementId> {
|
|
||||||
self.0.element_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||||
self.0.layout(cx)
|
self.0.layout(cx)
|
||||||
}
|
}
|
||||||
|
@ -464,6 +460,10 @@ impl AnyElement {
|
||||||
pub fn into_any(self) -> AnyElement {
|
pub fn into_any(self) -> AnyElement {
|
||||||
AnyElement::new(self)
|
AnyElement::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inner_id(&self) -> Option<ElementId> {
|
||||||
|
self.0.element_id()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for AnyElement {
|
impl Element for AnyElement {
|
||||||
|
@ -487,7 +487,7 @@ impl IntoElement for AnyElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn element_id(&self) -> Option<ElementId> {
|
||||||
AnyElement::element_id(self)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
|
|
|
@ -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, IntoElement, LayoutId, Pixels, Point, Render, Size,
|
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
||||||
StyleRefinement, Styled, View, ViewContext, WindowContext,
|
Pixels, Point, Render, 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};
|
||||||
|
@ -210,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);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -248,7 +248,7 @@ impl<V: 'static + Render> IntoElement for View<V> {
|
||||||
type Element = View<V>;
|
type Element = View<V>;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn element_id(&self) -> Option<ElementId> {
|
||||||
Some(self.model.entity_id.into())
|
Some(ElementId::from_entity_id(self.model.entity_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
|
@ -260,7 +260,7 @@ impl IntoElement for AnyView {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn element_id(&self) -> Option<ElementId> {
|
fn element_id(&self) -> Option<ElementId> {
|
||||||
Some(self.model.entity_id.into())
|
Some(ElementId::from_entity_id(self.model.entity_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
|
@ -308,27 +308,23 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
mod any_view {
|
mod any_view {
|
||||||
use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext};
|
use crate::{AnyElement, AnyView, Element, LayoutId, Render, WindowContext};
|
||||||
|
|
||||||
pub(crate) fn layout<V: 'static + Render>(
|
pub(crate) fn layout<V: 'static + Render>(
|
||||||
view: &AnyView,
|
view: &AnyView,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (LayoutId, AnyElement) {
|
) -> (LayoutId, AnyElement) {
|
||||||
cx.with_element_id(Some(view.model.entity_id), |cx| {
|
let view = view.clone().downcast::<V>().unwrap();
|
||||||
let view = view.clone().downcast::<V>().unwrap();
|
let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
|
||||||
let mut element = view.update(cx, |view, cx| view.render(cx).into_any());
|
let layout_id = element.layout(cx);
|
||||||
let layout_id = element.layout(cx);
|
(layout_id, element)
|
||||||
(layout_id, element)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn paint<V: 'static + Render>(
|
pub(crate) fn paint<V: 'static + Render>(
|
||||||
view: &AnyView,
|
_view: &AnyView,
|
||||||
element: AnyElement,
|
element: AnyElement,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
cx.with_element_id(Some(view.model.entity_id), |cx| {
|
element.paint(cx);
|
||||||
element.paint(cx);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,9 +230,15 @@ pub struct Window {
|
||||||
pub(crate) focus: Option<FocusId>,
|
pub(crate) focus: Option<FocusId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ElementStateBox {
|
||||||
|
inner: Box<dyn Any>,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
type_name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
// #[derive(Default)]
|
// #[derive(Default)]
|
||||||
pub(crate) struct Frame {
|
pub(crate) struct Frame {
|
||||||
pub(crate) element_states: HashMap<GlobalElementId, Box<dyn Any>>,
|
pub(crate) element_states: HashMap<GlobalElementId, ElementStateBox>,
|
||||||
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
|
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
|
||||||
pub(crate) dispatch_tree: DispatchTree,
|
pub(crate) dispatch_tree: DispatchTree,
|
||||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||||
|
@ -1815,10 +1821,37 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||||
.remove(&global_id)
|
.remove(&global_id)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
let ElementStateBox {
|
||||||
|
inner,
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
type_name
|
||||||
|
} = any;
|
||||||
// Using the extra inner option to avoid needing to reallocate a new box.
|
// Using the extra inner option to avoid needing to reallocate a new box.
|
||||||
let mut state_box = any
|
let mut state_box = inner
|
||||||
.downcast::<Option<S>>()
|
.downcast::<Option<S>>()
|
||||||
.expect("invalid element state type for id");
|
.map_err(|_| {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
anyhow!(
|
||||||
|
"invalid element state type for id, requested_type {:?}, actual type: {:?}",
|
||||||
|
std::any::type_name::<S>(),
|
||||||
|
type_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
anyhow!(
|
||||||
|
"invalid element state type for id, requested_type {:?}",
|
||||||
|
std::any::type_name::<S>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Actual: Option<AnyElement> <- View
|
||||||
|
// Requested: () <- AnyElemet
|
||||||
let state = state_box
|
let state = state_box
|
||||||
.take()
|
.take()
|
||||||
.expect("element state is already on the stack");
|
.expect("element state is already on the stack");
|
||||||
|
@ -1827,14 +1860,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
|
||||||
cx.window_mut()
|
cx.window_mut()
|
||||||
.current_frame
|
.current_frame
|
||||||
.element_states
|
.element_states
|
||||||
.insert(global_id, state_box);
|
.insert(global_id, ElementStateBox {
|
||||||
|
inner: state_box,
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
type_name
|
||||||
|
});
|
||||||
result
|
result
|
||||||
} else {
|
} else {
|
||||||
let (result, state) = f(None, cx);
|
let (result, state) = f(None, cx);
|
||||||
cx.window_mut()
|
cx.window_mut()
|
||||||
.current_frame
|
.current_frame
|
||||||
.element_states
|
.element_states
|
||||||
.insert(global_id, Box::new(Some(state)));
|
.insert(global_id,
|
||||||
|
ElementStateBox {
|
||||||
|
inner: Box::new(Some(state)),
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
type_name: std::any::type_name::<S>()
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2599,6 +2645,12 @@ pub enum ElementId {
|
||||||
FocusHandle(FocusId),
|
FocusHandle(FocusId),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ElementId {
|
||||||
|
pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
|
||||||
|
ElementId::View(entity_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryInto<SharedString> for ElementId {
|
impl TryInto<SharedString> for ElementId {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
@ -2611,12 +2663,6 @@ impl TryInto<SharedString> for ElementId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EntityId> for ElementId {
|
|
||||||
fn from(id: EntityId) -> Self {
|
|
||||||
ElementId::View(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<usize> for ElementId {
|
impl From<usize> for ElementId {
|
||||||
fn from(id: usize) -> Self {
|
fn from(id: usize) -> Self {
|
||||||
ElementId::Integer(id)
|
ElementId::Integer(id)
|
||||||
|
|
|
@ -1056,7 +1056,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
||||||
async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
cx.executor().allow_parking();
|
cx.executor().allow_parking();
|
||||||
let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||||
|
|
||||||
let fs_fake = FakeFs::new(cx.background_executor.clone());
|
let fs_fake = FakeFs::new(cx.background_executor.clone());
|
||||||
fs_fake
|
fs_fake
|
||||||
|
@ -1096,7 +1096,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
||||||
assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
||||||
});
|
});
|
||||||
|
|
||||||
let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||||
|
|
||||||
let fs_real = Arc::new(RealFs);
|
let fs_real = Arc::new(RealFs);
|
||||||
let temp_root = temp_tree(json!({
|
let temp_root = temp_tree(json!({
|
||||||
|
@ -2181,7 +2181,7 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
|
fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
|
||||||
let http_client = FakeHttpClient::with_404_response();
|
let http_client = FakeHttpClient::with_404_response();
|
||||||
cx.read(|cx| Client::new(http_client, cx))
|
cx.update(|cx| Client::new(http_client, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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!()
|
||||||
|
|
|
@ -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().into_any_element(),
|
||||||
separator.clone().into_any_element()
|
ContextMenuItem::Header(header) => {
|
||||||
|
ListSubHeader::new(header.clone()).into_any_element()
|
||||||
}
|
}
|
||||||
ContextMenuItem::Header(header) => header.clone().into_any_element(),
|
|
||||||
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)
|
||||||
|
|
|
@ -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 RenderOnce 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(IntoElement, Clone)]
|
#[derive(IntoElement, Clone)]
|
||||||
pub struct ListSeparator;
|
pub struct ListSeparator;
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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);
|
||||||
panel.entity_id(),
|
})
|
||||||
Label::new(format!("Dock {}", position.to_label())),
|
|
||||||
),
|
|
||||||
move |_, cx| {
|
|
||||||
panel.set_position(position, cx);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menu
|
menu
|
||||||
|
|
|
@ -1350,7 +1350,7 @@ impl Pane {
|
||||||
let id = item.item_id();
|
let id = item.item_id();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(item.item_id())
|
.id(ix)
|
||||||
.invisible()
|
.invisible()
|
||||||
.group_hover("", |style| style.visible())
|
.group_hover("", |style| style.visible())
|
||||||
.child(
|
.child(
|
||||||
|
@ -1382,7 +1382,7 @@ impl Pane {
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.group("")
|
.group("")
|
||||||
.id(item.item_id())
|
.id(ix)
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
.when_some(item.tab_tooltip_text(cx), |div, text| {
|
||||||
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into())
|
||||||
|
|
|
@ -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>,
|
||||||
|
@ -214,7 +210,7 @@ impl Member {
|
||||||
// Some(pane)
|
// Some(pane)
|
||||||
// };
|
// };
|
||||||
|
|
||||||
div().size_full().child(pane.clone())
|
div().size_full().child(pane.clone()).into_any()
|
||||||
|
|
||||||
// Stack::new()
|
// Stack::new()
|
||||||
// .with_child(pane_element.contained().with_border(leader_border))
|
// .with_child(pane_element.contained().with_border(leader_border))
|
||||||
|
@ -230,16 +226,17 @@ impl Member {
|
||||||
// .bg(cx.theme().colors().editor)
|
// .bg(cx.theme().colors().editor)
|
||||||
// .children();
|
// .children();
|
||||||
}
|
}
|
||||||
Member::Axis(axis) => axis.render(
|
Member::Axis(axis) => axis
|
||||||
project,
|
.render(
|
||||||
basis + 1,
|
project,
|
||||||
follower_states,
|
basis + 1,
|
||||||
active_call,
|
follower_states,
|
||||||
active_pane,
|
active_pane,
|
||||||
zoomed,
|
zoomed,
|
||||||
app_state,
|
app_state,
|
||||||
cx,
|
cx,
|
||||||
),
|
)
|
||||||
|
.into_any(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// enum FollowIntoExternalProject {}
|
// enum FollowIntoExternalProject {}
|
||||||
|
@ -556,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>,
|
||||||
|
@ -578,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,
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -65,7 +65,8 @@ fn main() {
|
||||||
log::info!("========== starting zed ==========");
|
log::info!("========== starting zed ==========");
|
||||||
let mut app = gpui::App::new(Assets).unwrap();
|
let mut app = gpui::App::new(Assets).unwrap();
|
||||||
|
|
||||||
let installation_id = app.background().block(installation_id()).ok();
|
let (installation_id, existing_installation_id_found) =
|
||||||
|
app.background().block(installation_id()).ok().unzip();
|
||||||
let session_id = Uuid::new_v4().to_string();
|
let session_id = Uuid::new_v4().to_string();
|
||||||
init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
||||||
|
|
||||||
|
@ -166,6 +167,14 @@ fn main() {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
client.telemetry().start(installation_id, session_id, cx);
|
client.telemetry().start(installation_id, session_id, cx);
|
||||||
|
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
||||||
|
let event_operation = match existing_installation_id_found {
|
||||||
|
Some(false) => "first open",
|
||||||
|
_ => "open",
|
||||||
|
};
|
||||||
|
client
|
||||||
|
.telemetry()
|
||||||
|
.report_app_event(telemetry_settings, event_operation);
|
||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
|
@ -317,11 +326,11 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn installation_id() -> Result<String> {
|
async fn installation_id() -> Result<(String, bool)> {
|
||||||
let legacy_key_name = "device_id";
|
let legacy_key_name = "device_id";
|
||||||
|
|
||||||
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
|
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
|
||||||
Ok(installation_id)
|
Ok((installation_id, true))
|
||||||
} else {
|
} else {
|
||||||
let installation_id = Uuid::new_v4().to_string();
|
let installation_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
@ -329,7 +338,7 @@ async fn installation_id() -> Result<String> {
|
||||||
.write_kvp(legacy_key_name.to_string(), installation_id.clone())
|
.write_kvp(legacy_key_name.to_string(), installation_id.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(installation_id)
|
Ok((installation_id, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,11 @@ fn main() {
|
||||||
log::info!("========== starting zed ==========");
|
log::info!("========== starting zed ==========");
|
||||||
let app = App::production(Arc::new(Assets));
|
let app = App::production(Arc::new(Assets));
|
||||||
|
|
||||||
let installation_id = app.background_executor().block(installation_id()).ok();
|
let (installation_id, existing_installation_id_found) = app
|
||||||
|
.background_executor()
|
||||||
|
.block(installation_id())
|
||||||
|
.ok()
|
||||||
|
.unzip();
|
||||||
let session_id = Uuid::new_v4().to_string();
|
let session_id = Uuid::new_v4().to_string();
|
||||||
init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
||||||
|
|
||||||
|
@ -173,6 +177,14 @@ fn main() {
|
||||||
// .detach();
|
// .detach();
|
||||||
|
|
||||||
client.telemetry().start(installation_id, session_id, cx);
|
client.telemetry().start(installation_id, session_id, cx);
|
||||||
|
let telemetry_settings = *client::TelemetrySettings::get_global(cx);
|
||||||
|
let event_operation = match existing_installation_id_found {
|
||||||
|
Some(false) => "first open",
|
||||||
|
_ => "open",
|
||||||
|
};
|
||||||
|
client
|
||||||
|
.telemetry()
|
||||||
|
.report_app_event(telemetry_settings, event_operation);
|
||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
|
@ -180,6 +192,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,
|
||||||
|
@ -333,11 +346,11 @@ fn main() {
|
||||||
// Ok::<_, anyhow::Error>(())
|
// Ok::<_, anyhow::Error>(())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async fn installation_id() -> Result<String> {
|
async fn installation_id() -> Result<(String, bool)> {
|
||||||
let legacy_key_name = "device_id";
|
let legacy_key_name = "device_id";
|
||||||
|
|
||||||
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
|
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
|
||||||
Ok(installation_id)
|
Ok((installation_id, true))
|
||||||
} else {
|
} else {
|
||||||
let installation_id = Uuid::new_v4().to_string();
|
let installation_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
@ -345,7 +358,7 @@ async fn installation_id() -> Result<String> {
|
||||||
.write_kvp(legacy_key_name.to_string(), installation_id.clone())
|
.write_kvp(legacy_key_name.to_string(), installation_id.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(installation_id)
|
Ok((installation_id, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue