Show cursors for remote participants

This commit is contained in:
Conrad Irwin 2024-01-16 21:43:44 -07:00
parent 26a3f68080
commit 0ca9f286c6
6 changed files with 122 additions and 4 deletions

View File

@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, Future, StreamExt};
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task};
use gpui::{
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUrl, Task,
WeakModel,
};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak};
@ -77,6 +80,7 @@ pub struct UserStore {
client: Weak<Client>,
_maintain_contacts: Task<()>,
_maintain_current_user: Task<Result<()>>,
weak_self: WeakModel<Self>,
}
#[derive(Clone)]
@ -194,6 +198,7 @@ impl UserStore {
Ok(())
}),
pending_contact_requests: Default::default(),
weak_self: cx.weak_model(),
}
}
@ -579,6 +584,19 @@ impl UserStore {
self.users.get(&user_id).cloned()
}
pub fn get_user_optimistic(
&mut self,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Option<Arc<User>> {
if let Some(user) = self.users.get(&user_id).cloned() {
return Some(user);
}
self.get_user(user_id, cx).detach_and_log_err(cx);
None
}
pub fn get_user(
&mut self,
user_id: u64,
@ -617,6 +635,7 @@ impl UserStore {
cx.spawn(|this, mut cx| async move {
if let Some(rpc) = client.upgrade() {
let response = rpc.request(request).await.context("error loading users")?;
dbg!(&response.users);
let users = response
.users
.into_iter()
@ -651,6 +670,31 @@ impl UserStore {
pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
&self.participant_indices
}
pub fn participant_names(
&self,
user_ids: impl Iterator<Item = u64>,
cx: &AppContext,
) -> HashMap<u64, SharedString> {
let mut ret = HashMap::default();
let mut missing_user_ids = Vec::new();
for id in user_ids {
if let Some(github_login) = self.get_cached_user(id).map(|u| u.github_login.clone()) {
ret.insert(id, github_login.into());
} else {
missing_user_ids.push(id)
}
}
if !missing_user_ids.is_empty() {
let this = self.weak_self.clone();
cx.spawn(|mut cx| async move {
this.update(&mut cx, |this, cx| this.get_users(missing_user_ids, cx))?
.await
})
.detach_and_log_err(cx);
}
ret
}
}
impl User {

View File

@ -442,4 +442,13 @@ impl CollaborationHub for ChannelBufferCollaborationHub {
) -> &'a HashMap<u64, ParticipantIndex> {
self.0.read(cx).user_store().read(cx).participant_indices()
}
fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
let user_ids = self.collaborators(cx).values().map(|c| c.user_id);
self.0
.read(cx)
.user_store()
.read(cx)
.participant_names(user_ids, cx)
}
}

View File

@ -627,6 +627,7 @@ pub struct RemoteSelection {
pub peer_id: PeerId,
pub line_mode: bool,
pub participant_index: Option<ParticipantIndex>,
pub user_name: Option<SharedString>,
}
#[derive(Clone, Debug)]
@ -9246,6 +9247,7 @@ pub trait CollaborationHub {
&self,
cx: &'a AppContext,
) -> &'a HashMap<u64, ParticipantIndex>;
fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
}
impl CollaborationHub for Model<Project> {
@ -9259,6 +9261,14 @@ impl CollaborationHub for Model<Project> {
) -> &'a HashMap<u64, ParticipantIndex> {
self.read(cx).user_store().read(cx).participant_indices()
}
fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
let this = self.read(cx);
let user_ids = this.collaborators().values().map(|c| c.user_id);
this.user_store().read_with(cx, |user_store, cx| {
user_store.participant_names(user_ids, cx)
})
}
}
fn inlay_hint_settings(
@ -9310,6 +9320,7 @@ impl EditorSnapshot {
collaboration_hub: &dyn CollaborationHub,
cx: &'a AppContext,
) -> impl 'a + Iterator<Item = RemoteSelection> {
let participant_names = collaboration_hub.user_names(cx);
let participant_indices = collaboration_hub.user_participant_indices(cx);
let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
let collaborators_by_replica_id = collaborators_by_peer_id
@ -9321,6 +9332,7 @@ impl EditorSnapshot {
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
let participant_index = participant_indices.get(&collaborator.user_id).copied();
let user_name = participant_names.get(&collaborator.user_id).cloned();
Some(RemoteSelection {
replica_id,
selection,
@ -9328,6 +9340,7 @@ impl EditorSnapshot {
line_mode,
participant_index,
peer_id: collaborator.peer_id,
user_name,
})
})
}

View File

@ -64,6 +64,7 @@ struct SelectionLayout {
is_local: bool,
range: Range<DisplayPoint>,
active_rows: Range<u32>,
user_name: Option<SharedString>,
}
impl SelectionLayout {
@ -74,6 +75,7 @@ impl SelectionLayout {
map: &DisplaySnapshot,
is_newest: bool,
is_local: bool,
user_name: Option<SharedString>,
) -> Self {
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
let display_selection = point_selection.map(|p| p.to_display_point(map));
@ -113,6 +115,7 @@ impl SelectionLayout {
is_local,
range,
active_rows,
user_name,
}
}
}
@ -980,8 +983,10 @@ impl EditorElement {
let corner_radius = 0.15 * layout.position_map.line_height;
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
for (selection_style, selections) in &layout.selections {
for selection in selections {
for (participant_ix, (selection_style, selections)) in
layout.selections.iter().enumerate()
{
for selection in selections.into_iter() {
self.paint_highlighted_range(
selection.range.clone(),
selection_style.selection,
@ -1062,6 +1067,7 @@ impl EditorElement {
))
});
}
cursors.push(Cursor {
color: selection_style.cursor,
block_width,
@ -1069,6 +1075,14 @@ impl EditorElement {
line_height: layout.position_map.line_height,
shape: selection.cursor_shape,
block_text,
cursor_name: selection.user_name.clone().map(|name| {
CursorName {
string: name,
color: self.style.background,
is_top_row: cursor_position.row() == 0,
z_index: (participant_ix % 256).try_into().unwrap(),
}
}),
});
}
}
@ -1887,6 +1901,7 @@ impl EditorElement {
&snapshot.display_snapshot,
is_newest,
true,
None,
);
if is_newest {
newest_selection_head = Some(layout.head);
@ -1959,6 +1974,7 @@ impl EditorElement {
&snapshot.display_snapshot,
false,
false,
selection.user_name,
));
}
@ -1990,6 +2006,7 @@ impl EditorElement {
&snapshot.display_snapshot,
true,
true,
None,
)
.head
});
@ -3096,6 +3113,15 @@ pub struct Cursor {
color: Hsla,
shape: CursorShape,
block_text: Option<ShapedLine>,
cursor_name: Option<CursorName>,
}
#[derive(Debug)]
pub struct CursorName {
string: SharedString,
color: Hsla,
is_top_row: bool,
z_index: u8,
}
impl Cursor {
@ -3106,6 +3132,7 @@ impl Cursor {
color: Hsla,
shape: CursorShape,
block_text: Option<ShapedLine>,
cursor_name: Option<CursorName>,
) -> Cursor {
Cursor {
origin,
@ -3114,6 +3141,7 @@ impl Cursor {
color,
shape,
block_text,
cursor_name,
}
}
@ -3156,6 +3184,29 @@ impl Cursor {
.paint(self.origin + origin, self.line_height, cx)
.log_err();
}
if let Some(name) = &self.cursor_name {
let name_origin = if name.is_top_row {
point(bounds.right() - px(1.), bounds.top())
} else {
point(bounds.left(), bounds.top() - self.line_height / 4. - px(1.))
};
cx.with_z_index(name.z_index, |cx| {
div()
.bg(self.color)
.text_size(self.line_height / 2.)
.px_0p5()
.line_height(self.line_height / 2. + px(1.))
.text_color(name.color)
.child(name.string.clone())
.into_any_element()
.draw(
name_origin,
size(AvailableSpace::MinContent, AvailableSpace::MinContent),
cx,
)
})
}
}
pub fn shape(&self) -> CursorShape {

View File

@ -68,7 +68,7 @@ where
/// Run the task to completion in the background and log any
/// errors that occur.
#[track_caller]
pub fn detach_and_log_err(self, cx: &mut AppContext) {
pub fn detach_and_log_err(self, cx: &AppContext) {
let location = core::panic::Location::caller();
cx.foreground_executor()
.spawn(self.log_tracked_err(*location))

View File

@ -550,6 +550,7 @@ impl TerminalElement {
theme.players().local().cursor,
shape,
text,
None,
)
},
)