mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 18:41:56 +03:00
Show cursors for remote participants (#4082)
This PR enables cursors for remote participants. They are shown for 2 seconds when you focus a buffer, and then on hover. Release Notes: - Added usernames next to remote cursors
This commit is contained in:
commit
d76bd100f5
@ -1,6 +1,6 @@
|
|||||||
use crate::{Channel, ChannelId, ChannelStore};
|
use crate::{Channel, ChannelId, ChannelStore};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::{Client, Collaborator, UserStore};
|
use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||||
use language::proto::serialize_version;
|
use language::proto::serialize_version;
|
||||||
@ -181,6 +181,16 @@ impl ChannelBuffer {
|
|||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
language::Event::Operation(operation) => {
|
language::Event::Operation(operation) => {
|
||||||
|
if *ZED_ALWAYS_ACTIVE {
|
||||||
|
match operation {
|
||||||
|
language::Operation::UpdateSelections { selections, .. } => {
|
||||||
|
if selections.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
let operation = language::proto::serialize_operation(operation);
|
let operation = language::proto::serialize_operation(operation);
|
||||||
self.client
|
self.client
|
||||||
.send(proto::UpdateChannelBuffer {
|
.send(proto::UpdateChannelBuffer {
|
||||||
|
@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result};
|
|||||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use futures::{channel::mpsc, Future, StreamExt};
|
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 postage::{sink::Sink, watch};
|
||||||
use rpc::proto::{RequestMessage, UsersResponse};
|
use rpc::proto::{RequestMessage, UsersResponse};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@ -77,6 +80,7 @@ pub struct UserStore {
|
|||||||
client: Weak<Client>,
|
client: Weak<Client>,
|
||||||
_maintain_contacts: Task<()>,
|
_maintain_contacts: Task<()>,
|
||||||
_maintain_current_user: Task<Result<()>>,
|
_maintain_current_user: Task<Result<()>>,
|
||||||
|
weak_self: WeakModel<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -194,6 +198,7 @@ impl UserStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
pending_contact_requests: Default::default(),
|
pending_contact_requests: Default::default(),
|
||||||
|
weak_self: cx.weak_model(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,6 +584,19 @@ impl UserStore {
|
|||||||
self.users.get(&user_id).cloned()
|
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(
|
pub fn get_user(
|
||||||
&mut self,
|
&mut self,
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
@ -651,6 +669,31 @@ impl UserStore {
|
|||||||
pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
|
pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
|
||||||
&self.participant_indices
|
&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 {
|
impl User {
|
||||||
|
@ -450,8 +450,21 @@ impl Database {
|
|||||||
)> {
|
)> {
|
||||||
self.transaction(move |tx| async move {
|
self.transaction(move |tx| async move {
|
||||||
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
let channel = self.get_channel_internal(channel_id, &*tx).await?;
|
||||||
|
|
||||||
|
let mut requires_write_permission = false;
|
||||||
|
for op in operations.iter() {
|
||||||
|
match op.variant {
|
||||||
|
None | Some(proto::operation::Variant::UpdateSelections(_)) => {}
|
||||||
|
Some(_) => requires_write_permission = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if requires_write_permission {
|
||||||
self.check_user_is_channel_member(&channel, user, &*tx)
|
self.check_user_is_channel_member(&channel, user, &*tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
} else {
|
||||||
|
self.check_user_is_channel_participant(&channel, user, &*tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
let buffer = buffer::Entity::find()
|
let buffer = buffer::Entity::find()
|
||||||
.filter(buffer::Column::ChannelId.eq(channel_id))
|
.filter(buffer::Column::ChannelId.eq(channel_id))
|
||||||
|
@ -442,4 +442,13 @@ impl CollaborationHub for ChannelBufferCollaborationHub {
|
|||||||
) -> &'a HashMap<u64, ParticipantIndex> {
|
) -> &'a HashMap<u64, ParticipantIndex> {
|
||||||
self.0.read(cx).user_store().read(cx).participant_indices()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,6 +367,8 @@ pub struct Editor {
|
|||||||
project: Option<Model<Project>>,
|
project: Option<Model<Project>>,
|
||||||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||||
blink_manager: Model<BlinkManager>,
|
blink_manager: Model<BlinkManager>,
|
||||||
|
recently_focused: bool,
|
||||||
|
hovered_cursor: Option<HoveredCursor>,
|
||||||
pub show_local_selections: bool,
|
pub show_local_selections: bool,
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
show_gutter: bool,
|
show_gutter: bool,
|
||||||
@ -418,6 +420,7 @@ pub struct EditorSnapshot {
|
|||||||
ongoing_scroll: OngoingScroll,
|
ongoing_scroll: OngoingScroll,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RemoteSelection {
|
pub struct RemoteSelection {
|
||||||
pub replica_id: ReplicaId,
|
pub replica_id: ReplicaId,
|
||||||
pub selection: Selection<Anchor>,
|
pub selection: Selection<Anchor>,
|
||||||
@ -425,6 +428,7 @@ pub struct RemoteSelection {
|
|||||||
pub peer_id: PeerId,
|
pub peer_id: PeerId,
|
||||||
pub line_mode: bool,
|
pub line_mode: bool,
|
||||||
pub participant_index: Option<ParticipantIndex>,
|
pub participant_index: Option<ParticipantIndex>,
|
||||||
|
pub user_name: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -441,6 +445,11 @@ enum SelectionHistoryMode {
|
|||||||
Redoing,
|
Redoing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HoveredCursor {
|
||||||
|
replica_id: u16,
|
||||||
|
selection_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for SelectionHistoryMode {
|
impl Default for SelectionHistoryMode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Normal
|
Self::Normal
|
||||||
@ -1604,6 +1613,8 @@ impl Editor {
|
|||||||
pixel_position_of_newest_cursor: None,
|
pixel_position_of_newest_cursor: None,
|
||||||
gutter_width: Default::default(),
|
gutter_width: Default::default(),
|
||||||
style: None,
|
style: None,
|
||||||
|
recently_focused: false,
|
||||||
|
hovered_cursor: Default::default(),
|
||||||
editor_actions: Default::default(),
|
editor_actions: Default::default(),
|
||||||
show_copilot_suggestions: mode == EditorMode::Full,
|
show_copilot_suggestions: mode == EditorMode::Full,
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
@ -8992,6 +9003,17 @@ impl Editor {
|
|||||||
cx.focus(&rename_editor_focus_handle);
|
cx.focus(&rename_editor_focus_handle);
|
||||||
} else {
|
} else {
|
||||||
self.blink_manager.update(cx, BlinkManager::enable);
|
self.blink_manager.update(cx, BlinkManager::enable);
|
||||||
|
self.recently_focused = true;
|
||||||
|
cx.notify();
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.recently_focused = false;
|
||||||
|
cx.notify()
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.finalize_last_transaction(cx);
|
buffer.finalize_last_transaction(cx);
|
||||||
if self.leader_peer_id.is_none() {
|
if self.leader_peer_id.is_none() {
|
||||||
@ -9043,6 +9065,7 @@ pub trait CollaborationHub {
|
|||||||
&self,
|
&self,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> &'a HashMap<u64, ParticipantIndex>;
|
) -> &'a HashMap<u64, ParticipantIndex>;
|
||||||
|
fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollaborationHub for Model<Project> {
|
impl CollaborationHub for Model<Project> {
|
||||||
@ -9056,6 +9079,14 @@ impl CollaborationHub for Model<Project> {
|
|||||||
) -> &'a HashMap<u64, ParticipantIndex> {
|
) -> &'a HashMap<u64, ParticipantIndex> {
|
||||||
self.read(cx).user_store().read(cx).participant_indices()
|
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(
|
fn inlay_hint_settings(
|
||||||
@ -9107,6 +9138,7 @@ impl EditorSnapshot {
|
|||||||
collaboration_hub: &dyn CollaborationHub,
|
collaboration_hub: &dyn CollaborationHub,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> impl 'a + Iterator<Item = RemoteSelection> {
|
) -> impl 'a + Iterator<Item = RemoteSelection> {
|
||||||
|
let participant_names = collaboration_hub.user_names(cx);
|
||||||
let participant_indices = collaboration_hub.user_participant_indices(cx);
|
let participant_indices = collaboration_hub.user_participant_indices(cx);
|
||||||
let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
|
let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
|
||||||
let collaborators_by_replica_id = collaborators_by_peer_id
|
let collaborators_by_replica_id = collaborators_by_peer_id
|
||||||
@ -9118,6 +9150,7 @@ impl EditorSnapshot {
|
|||||||
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
|
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
|
||||||
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
|
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
|
||||||
let participant_index = participant_indices.get(&collaborator.user_id).copied();
|
let participant_index = participant_indices.get(&collaborator.user_id).copied();
|
||||||
|
let user_name = participant_names.get(&collaborator.user_id).cloned();
|
||||||
Some(RemoteSelection {
|
Some(RemoteSelection {
|
||||||
replica_id,
|
replica_id,
|
||||||
selection,
|
selection,
|
||||||
@ -9125,6 +9158,7 @@ impl EditorSnapshot {
|
|||||||
line_mode,
|
line_mode,
|
||||||
participant_index,
|
participant_index,
|
||||||
peer_id: collaborator.peer_id,
|
peer_id: collaborator.peer_id,
|
||||||
|
user_name,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ struct SelectionLayout {
|
|||||||
is_local: bool,
|
is_local: bool,
|
||||||
range: Range<DisplayPoint>,
|
range: Range<DisplayPoint>,
|
||||||
active_rows: Range<u32>,
|
active_rows: Range<u32>,
|
||||||
|
user_name: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionLayout {
|
impl SelectionLayout {
|
||||||
@ -74,6 +75,7 @@ impl SelectionLayout {
|
|||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
is_newest: bool,
|
is_newest: bool,
|
||||||
is_local: bool,
|
is_local: bool,
|
||||||
|
user_name: Option<SharedString>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||||
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
let display_selection = point_selection.map(|p| p.to_display_point(map));
|
||||||
@ -113,6 +115,7 @@ impl SelectionLayout {
|
|||||||
is_local,
|
is_local,
|
||||||
range,
|
range,
|
||||||
active_rows,
|
active_rows,
|
||||||
|
user_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,6 +567,7 @@ impl EditorElement {
|
|||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
hover_at(editor, Some(point), cx);
|
hover_at(editor, Some(point), cx);
|
||||||
|
Self::update_visible_cursor(editor, point, cx);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
update_inlay_link_and_hover_points(
|
update_inlay_link_and_hover_points(
|
||||||
@ -585,6 +589,39 @@ impl EditorElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_visible_cursor(
|
||||||
|
editor: &mut Editor,
|
||||||
|
point: DisplayPoint,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let snapshot = editor.snapshot(cx);
|
||||||
|
let Some(hub) = editor.collaboration_hub() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
|
||||||
|
..DisplayPoint::new(
|
||||||
|
point.row(),
|
||||||
|
(point.column() + 1).min(snapshot.line_len(point.row())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let range = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
|
||||||
|
..snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
|
||||||
|
|
||||||
|
let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
|
||||||
|
editor.hovered_cursor.take();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
editor.hovered_cursor.replace(crate::HoveredCursor {
|
||||||
|
replica_id: selection.replica_id,
|
||||||
|
selection_id: selection.selection.id,
|
||||||
|
});
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
|
||||||
fn paint_background(
|
fn paint_background(
|
||||||
&self,
|
&self,
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
@ -982,8 +1019,10 @@ impl EditorElement {
|
|||||||
let corner_radius = 0.15 * layout.position_map.line_height;
|
let corner_radius = 0.15 * layout.position_map.line_height;
|
||||||
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
|
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
|
||||||
|
|
||||||
for (selection_style, selections) in &layout.selections {
|
for (participant_ix, (selection_style, selections)) in
|
||||||
for selection in selections {
|
layout.selections.iter().enumerate()
|
||||||
|
{
|
||||||
|
for selection in selections.into_iter() {
|
||||||
self.paint_highlighted_range(
|
self.paint_highlighted_range(
|
||||||
selection.range.clone(),
|
selection.range.clone(),
|
||||||
selection_style.selection,
|
selection_style.selection,
|
||||||
@ -1064,6 +1103,7 @@ impl EditorElement {
|
|||||||
))
|
))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cursors.push(Cursor {
|
cursors.push(Cursor {
|
||||||
color: selection_style.cursor,
|
color: selection_style.cursor,
|
||||||
block_width,
|
block_width,
|
||||||
@ -1071,6 +1111,14 @@ impl EditorElement {
|
|||||||
line_height: layout.position_map.line_height,
|
line_height: layout.position_map.line_height,
|
||||||
shape: selection.cursor_shape,
|
shape: selection.cursor_shape,
|
||||||
block_text,
|
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(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1889,6 +1937,7 @@ impl EditorElement {
|
|||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
is_newest,
|
is_newest,
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
if is_newest {
|
if is_newest {
|
||||||
newest_selection_head = Some(layout.head);
|
newest_selection_head = Some(layout.head);
|
||||||
@ -1949,6 +1998,7 @@ impl EditorElement {
|
|||||||
if Some(selection.peer_id) == editor.leader_peer_id {
|
if Some(selection.peer_id) == editor.leader_peer_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let is_shown = editor.recently_focused || editor.hovered_cursor.as_ref().is_some_and(|c| c.replica_id == selection.replica_id && c.selection_id == selection.selection.id);
|
||||||
|
|
||||||
remote_selections
|
remote_selections
|
||||||
.entry(selection.replica_id)
|
.entry(selection.replica_id)
|
||||||
@ -1961,6 +2011,11 @@ impl EditorElement {
|
|||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
if is_shown {
|
||||||
|
selection.user_name
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1992,6 +2047,7 @@ impl EditorElement {
|
|||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.head
|
.head
|
||||||
});
|
});
|
||||||
@ -3097,6 +3153,15 @@ pub struct Cursor {
|
|||||||
color: Hsla,
|
color: Hsla,
|
||||||
shape: CursorShape,
|
shape: CursorShape,
|
||||||
block_text: Option<ShapedLine>,
|
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 {
|
impl Cursor {
|
||||||
@ -3107,6 +3172,7 @@ impl Cursor {
|
|||||||
color: Hsla,
|
color: Hsla,
|
||||||
shape: CursorShape,
|
shape: CursorShape,
|
||||||
block_text: Option<ShapedLine>,
|
block_text: Option<ShapedLine>,
|
||||||
|
cursor_name: Option<CursorName>,
|
||||||
) -> Cursor {
|
) -> Cursor {
|
||||||
Cursor {
|
Cursor {
|
||||||
origin,
|
origin,
|
||||||
@ -3115,6 +3181,7 @@ impl Cursor {
|
|||||||
color,
|
color,
|
||||||
shape,
|
shape,
|
||||||
block_text,
|
block_text,
|
||||||
|
cursor_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3150,6 +3217,31 @@ impl Cursor {
|
|||||||
fill(bounds, self.color)
|
fill(bounds, self.color)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(name) = &self.cursor_name {
|
||||||
|
let text_size = self.line_height / 1.5;
|
||||||
|
|
||||||
|
let name_origin = if name.is_top_row {
|
||||||
|
point(bounds.right() - px(1.), bounds.top())
|
||||||
|
} else {
|
||||||
|
point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
|
||||||
|
};
|
||||||
|
cx.with_z_index(name.z_index, |cx| {
|
||||||
|
div()
|
||||||
|
.bg(self.color)
|
||||||
|
.text_size(text_size)
|
||||||
|
.px_0p5()
|
||||||
|
.line_height(text_size + px(2.))
|
||||||
|
.text_color(name.color)
|
||||||
|
.child(name.string.clone())
|
||||||
|
.into_any_element()
|
||||||
|
.draw(
|
||||||
|
name_origin,
|
||||||
|
size(AvailableSpace::MinContent, AvailableSpace::MinContent),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
cx.paint_quad(cursor);
|
cx.paint_quad(cursor);
|
||||||
|
|
||||||
if let Some(block_text) = &self.block_text {
|
if let Some(block_text) = &self.block_text {
|
||||||
|
@ -68,7 +68,7 @@ where
|
|||||||
/// Run the task to completion in the background and log any
|
/// Run the task to completion in the background and log any
|
||||||
/// errors that occur.
|
/// errors that occur.
|
||||||
#[track_caller]
|
#[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();
|
let location = core::panic::Location::caller();
|
||||||
cx.foreground_executor()
|
cx.foreground_executor()
|
||||||
.spawn(self.log_tracked_err(*location))
|
.spawn(self.log_tracked_err(*location))
|
||||||
|
@ -550,6 +550,7 @@ impl TerminalElement {
|
|||||||
theme.players().local().cursor,
|
theme.players().local().cursor,
|
||||||
shape,
|
shape,
|
||||||
text,
|
text,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user