Merge branch 'main' into z-index-with-flicker-fix
This commit is contained in:
commit
86b363d7f2
129 changed files with 4283 additions and 2313 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1548,6 +1548,7 @@ dependencies = [
|
|||
"log",
|
||||
"menu",
|
||||
"notifications",
|
||||
"parking_lot 0.11.2",
|
||||
"picker",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
|
@ -6144,6 +6145,7 @@ dependencies = [
|
|||
"smol",
|
||||
"sum_tree",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
|
||||
|
@ -9079,6 +9081,7 @@ dependencies = [
|
|||
"nvim-rs",
|
||||
"parking_lot 0.11.2",
|
||||
"project",
|
||||
"regex",
|
||||
"search",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
"context": "Editor && mode == auto_height",
|
||||
"bindings": {
|
||||
"ctrl-enter": "editor::Newline",
|
||||
"shift-enter": "editor::Newline",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -104,8 +104,6 @@
|
|||
"shift-v": "vim::ToggleVisualLine",
|
||||
"ctrl-v": "vim::ToggleVisualBlock",
|
||||
"ctrl-q": "vim::ToggleVisualBlock",
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"ctrl-f": "vim::PageDown",
|
||||
"pagedown": "vim::PageDown",
|
||||
|
@ -329,6 +327,8 @@
|
|||
"backwards": true
|
||||
}
|
||||
],
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
";": "vim::RepeatFind",
|
||||
",": [
|
||||
"vim::RepeatFind",
|
||||
|
@ -421,6 +421,18 @@
|
|||
"shift-r": "vim::SubstituteLine",
|
||||
"c": "vim::Substitute",
|
||||
"~": "vim::ChangeCase",
|
||||
"*": [
|
||||
"vim::MoveToNext",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
],
|
||||
"#": [
|
||||
"vim::MoveToPrev",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
],
|
||||
"ctrl-a": "vim::Increment",
|
||||
"ctrl-x": "vim::Decrement",
|
||||
"g ctrl-a": [
|
||||
|
|
|
@ -72,6 +72,9 @@
|
|||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
// Whether to automatically type closing characters for you. For example,
|
||||
// when you type (, Zed will automatically add a closing ) at the correct position.
|
||||
"use_autoclose": true,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
|
|
|
@ -134,7 +134,7 @@ pub async fn stream_completion(
|
|||
line: Result<String, io::Error>,
|
||||
) -> Result<Option<OpenAIResponseStreamEvent>> {
|
||||
if let Some(data) = line?.strip_prefix("data: ") {
|
||||
let event = serde_json::from_str(&data)?;
|
||||
let event = serde_json::from_str(data)?;
|
||||
Ok(Some(event))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
|
|
@ -1432,7 +1432,7 @@ impl Room {
|
|||
let display = displays
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("no display found"))?;
|
||||
let track = LocalVideoTrack::screen_share_for_display(&display);
|
||||
let track = LocalVideoTrack::screen_share_for_display(display);
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, _| {
|
||||
|
|
|
@ -86,11 +86,11 @@ pub struct ChannelPathsInsertGuard<'a> {
|
|||
|
||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
||||
insert_note_changed(&mut self.channels_by_id, channel_id, epoch, &version);
|
||||
insert_note_changed(self.channels_by_id, channel_id, epoch, version);
|
||||
}
|
||||
|
||||
pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
|
||||
insert_new_message(&mut self.channels_by_id, channel_id, message_id)
|
||||
insert_new_message(self.channels_by_id, channel_id, message_id)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||
|
@ -131,8 +131,8 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
|||
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.channels_ordered.sort_by(|a, b| {
|
||||
let a = channel_path_sorting_key(*a, &self.channels_by_id);
|
||||
let b = channel_path_sorting_key(*b, &self.channels_by_id);
|
||||
let a = channel_path_sorting_key(*a, self.channels_by_id);
|
||||
let b = channel_path_sorting_key(*b, self.channels_by_id);
|
||||
a.cmp(b)
|
||||
});
|
||||
self.channels_ordered.dedup();
|
||||
|
@ -167,7 +167,7 @@ fn insert_note_changed(
|
|||
if epoch > unseen_version.0 {
|
||||
*unseen_version = (epoch, version.clone());
|
||||
} else {
|
||||
unseen_version.1.join(&version);
|
||||
unseen_version.1.join(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1310,7 +1310,7 @@ impl Client {
|
|||
drop(state);
|
||||
|
||||
if let Some(handler) = handler {
|
||||
let future = handler(subscriber, message, &self, cx.clone());
|
||||
let future = handler(subscriber, message, self, cx.clone());
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
|
||||
|
|
|
@ -38,8 +38,9 @@ struct TelemetryState {
|
|||
flush_events_task: Option<Task<()>>,
|
||||
log_file: Option<NamedTempFile>,
|
||||
is_staff: Option<bool>,
|
||||
first_event_datetime: Option<DateTime<Utc>>,
|
||||
first_event_date_time: Option<DateTime<Utc>>,
|
||||
event_coalescer: EventCoalescer,
|
||||
max_queue_size: usize,
|
||||
}
|
||||
|
||||
const EVENTS_URL_PATH: &'static str = "/api/events";
|
||||
|
@ -69,14 +70,14 @@ struct EventWrapper {
|
|||
event: Event,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssistantKind {
|
||||
Panel,
|
||||
Inline,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Event {
|
||||
Editor {
|
||||
|
@ -168,8 +169,9 @@ impl Telemetry {
|
|||
flush_events_task: None,
|
||||
log_file: None,
|
||||
is_staff: None,
|
||||
first_event_datetime: None,
|
||||
first_event_date_time: None,
|
||||
event_coalescer: EventCoalescer::new(),
|
||||
max_queue_size: MAX_QUEUE_LEN,
|
||||
}));
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
@ -310,7 +312,7 @@ impl Telemetry {
|
|||
operation,
|
||||
copilot_enabled,
|
||||
copilot_enabled_for_language,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -326,7 +328,7 @@ impl Telemetry {
|
|||
suggestion_id,
|
||||
suggestion_accepted,
|
||||
file_extension,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -342,7 +344,7 @@ impl Telemetry {
|
|||
conversation_id,
|
||||
kind,
|
||||
model,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -358,7 +360,7 @@ impl Telemetry {
|
|||
operation,
|
||||
room_id,
|
||||
channel_id,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -368,7 +370,7 @@ impl Telemetry {
|
|||
let event = Event::Cpu {
|
||||
usage_as_percentage,
|
||||
core_count,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -382,26 +384,36 @@ impl Telemetry {
|
|||
let event = Event::Memory {
|
||||
memory_in_bytes,
|
||||
virtual_memory_in_bytes,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
pub fn report_app_event(self: &Arc<Self>, operation: String) {
|
||||
self.report_app_event_with_date_time(operation, Utc::now());
|
||||
}
|
||||
|
||||
fn report_app_event_with_date_time(
|
||||
self: &Arc<Self>,
|
||||
operation: String,
|
||||
date_time: DateTime<Utc>,
|
||||
) -> Event {
|
||||
let event = Event::App {
|
||||
operation,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(date_time),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
self.report_event(event.clone());
|
||||
|
||||
event
|
||||
}
|
||||
|
||||
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
|
||||
let event = Event::Setting {
|
||||
setting,
|
||||
value,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
|
@ -416,7 +428,7 @@ impl Telemetry {
|
|||
let event = Event::Edit {
|
||||
duration: end.timestamp_millis() - start.timestamp_millis(),
|
||||
environment,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event);
|
||||
|
@ -427,22 +439,21 @@ impl Telemetry {
|
|||
let event = Event::Action {
|
||||
source,
|
||||
action,
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
||||
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
|
||||
};
|
||||
|
||||
self.report_event(event)
|
||||
}
|
||||
|
||||
fn milliseconds_since_first_event(&self) -> i64 {
|
||||
fn milliseconds_since_first_event(self: &Arc<Self>, date_time: DateTime<Utc>) -> i64 {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
match state.first_event_datetime {
|
||||
Some(first_event_datetime) => {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
now.timestamp_millis() - first_event_datetime.timestamp_millis()
|
||||
match state.first_event_date_time {
|
||||
Some(first_event_date_time) => {
|
||||
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
|
||||
}
|
||||
None => {
|
||||
state.first_event_datetime = Some(Utc::now());
|
||||
state.first_event_date_time = Some(date_time);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +479,7 @@ impl Telemetry {
|
|||
state.events_queue.push(EventWrapper { signed_in, event });
|
||||
|
||||
if state.installation_id.is_some() {
|
||||
if state.events_queue.len() >= MAX_QUEUE_LEN {
|
||||
if state.events_queue.len() >= state.max_queue_size {
|
||||
drop(state);
|
||||
self.flush_events();
|
||||
}
|
||||
|
@ -489,7 +500,7 @@ impl Telemetry {
|
|||
|
||||
pub fn flush_events(self: &Arc<Self>) {
|
||||
let mut state = self.state.lock();
|
||||
state.first_event_datetime = None;
|
||||
state.first_event_date_time = None;
|
||||
let mut events = mem::take(&mut state.events_queue);
|
||||
state.flush_events_task.take();
|
||||
drop(state);
|
||||
|
@ -548,3 +559,159 @@ impl Telemetry {
|
|||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use gpui::TestAppContext;
|
||||
use util::http::FakeHttpClient;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
cx.update(|cx| {
|
||||
let telemetry = Telemetry::new(http, cx);
|
||||
|
||||
telemetry.state.lock().max_queue_size = 4;
|
||||
telemetry.start(installation_id, session_id, cx);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
|
||||
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
|
||||
let operation = "test".to_string();
|
||||
|
||||
let event =
|
||||
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
assert_eq!(
|
||||
telemetry.state.lock().first_event_date_time,
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
let mut date_time = first_date_time + chrono::Duration::milliseconds(100);
|
||||
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 100
|
||||
}
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 2);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
assert_eq!(
|
||||
telemetry.state.lock().first_event_date_time,
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
date_time += chrono::Duration::milliseconds(100);
|
||||
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 200
|
||||
}
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 3);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
assert_eq!(
|
||||
telemetry.state.lock().first_event_date_time,
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
date_time += chrono::Duration::milliseconds(100);
|
||||
|
||||
// Adding a 4th event should cause a flush
|
||||
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 300
|
||||
}
|
||||
);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let http = FakeHttpClient::with_200_response();
|
||||
let installation_id = Some("installation_id".to_string());
|
||||
let session_id = "session_id".to_string();
|
||||
|
||||
cx.update(|cx| {
|
||||
let telemetry = Telemetry::new(http, cx);
|
||||
telemetry.state.lock().max_queue_size = 4;
|
||||
telemetry.start(installation_id, session_id, cx);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
|
||||
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
|
||||
let operation = "test".to_string();
|
||||
|
||||
let event =
|
||||
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::App {
|
||||
operation: operation.clone(),
|
||||
milliseconds_since_first_event: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
|
||||
assert!(telemetry.state.lock().flush_events_task.is_some());
|
||||
assert_eq!(
|
||||
telemetry.state.lock().first_event_date_time,
|
||||
Some(first_date_time)
|
||||
);
|
||||
|
||||
let duration = Duration::from_millis(1);
|
||||
|
||||
// Test 1 millisecond before the flush interval limit is met
|
||||
executor.advance_clock(FLUSH_INTERVAL - duration);
|
||||
|
||||
assert!(!is_empty_state(&telemetry));
|
||||
|
||||
// Test the exact moment the flush interval limit is met
|
||||
executor.advance_clock(duration);
|
||||
|
||||
assert!(is_empty_state(&telemetry));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Test settings
|
||||
// Update FakeHTTPClient to keep track of the number of requests and assert on it
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
});
|
||||
}
|
||||
|
||||
fn is_empty_state(telemetry: &Telemetry) -> bool {
|
||||
telemetry.state.lock().events_queue.is_empty()
|
||||
&& telemetry.state.lock().flush_events_task.is_none()
|
||||
&& telemetry.state.lock().first_event_date_time.is_none()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ anyhow.workspace = true
|
|||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
schemars.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -18,7 +18,7 @@ use project::Fs;
|
|||
use rich_text::RichText;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{
|
||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
||||
|
@ -304,8 +304,11 @@ impl ChatPanel {
|
|||
let last_message = active_chat.message(ix.saturating_sub(1));
|
||||
let this_message = active_chat.message(ix).clone();
|
||||
|
||||
let is_continuation_from_previous = last_message.id != this_message.id
|
||||
&& last_message.sender.id == this_message.sender.id;
|
||||
let duration_since_last_message = this_message.timestamp - last_message.timestamp;
|
||||
let is_continuation_from_previous = last_message.sender.id
|
||||
== this_message.sender.id
|
||||
&& last_message.id != this_message.id
|
||||
&& duration_since_last_message < Duration::from_secs(5 * 60);
|
||||
|
||||
if let ChannelMessageId::Saved(id) = this_message.id {
|
||||
if this_message
|
||||
|
@ -325,8 +328,6 @@ impl ChatPanel {
|
|||
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
||||
});
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
||||
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
||||
(message.id, belongs_to_user || is_admin)
|
||||
|
@ -349,23 +350,21 @@ impl ChatPanel {
|
|||
.when(!is_continuation_from_previous, |this| {
|
||||
this.pt_3().child(
|
||||
h_flex()
|
||||
.child(
|
||||
div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone())
|
||||
.size(cx.rem_size() * 1.5),
|
||||
),
|
||||
)
|
||||
.text_ui_sm()
|
||||
.child(div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone()).size(cx.rem_size()),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.pl(cx.rem_size() * 1.5 + px(6.0))
|
||||
.pl(cx.rem_size() + px(6.0))
|
||||
.pr(px(8.0))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.child(Label::new(message.sender.github_login.clone())),
|
||||
)
|
||||
.child(
|
||||
Label::new(format_timestamp(
|
||||
OffsetDateTime::now_utc(),
|
||||
message.timestamp,
|
||||
now,
|
||||
self.local_timezone,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
|
@ -597,7 +596,7 @@ impl Render for ChatPanel {
|
|||
el.child(
|
||||
div()
|
||||
.rounded_md()
|
||||
.h_7()
|
||||
.h_6()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().editor_background),
|
||||
)
|
||||
|
@ -671,28 +670,44 @@ impl Panel for ChatPanel {
|
|||
impl EventEmitter<PanelEvent> for ChatPanel {}
|
||||
|
||||
fn format_timestamp(
|
||||
mut timestamp: OffsetDateTime,
|
||||
mut now: OffsetDateTime,
|
||||
local_timezone: UtcOffset,
|
||||
reference: OffsetDateTime,
|
||||
timestamp: OffsetDateTime,
|
||||
timezone: UtcOffset,
|
||||
) -> String {
|
||||
timestamp = timestamp.to_offset(local_timezone);
|
||||
now = now.to_offset(local_timezone);
|
||||
let timestamp_local = timestamp.to_offset(timezone);
|
||||
let timestamp_local_hour = timestamp_local.hour();
|
||||
|
||||
let today = now.date();
|
||||
let date = timestamp.date();
|
||||
let mut hour = timestamp.hour();
|
||||
let mut part = "am";
|
||||
if hour > 12 {
|
||||
hour -= 12;
|
||||
part = "pm";
|
||||
}
|
||||
if date == today {
|
||||
format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
} else if date.next_day() == Some(today) {
|
||||
format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
let hour_12 = match timestamp_local_hour {
|
||||
0 => 12, // Midnight
|
||||
13..=23 => timestamp_local_hour - 12, // PM hours
|
||||
_ => timestamp_local_hour, // AM hours
|
||||
};
|
||||
let meridiem = if timestamp_local_hour >= 12 {
|
||||
"pm"
|
||||
} else {
|
||||
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
|
||||
"am"
|
||||
};
|
||||
let timestamp_local_minute = timestamp_local.minute();
|
||||
let formatted_time = format!("{:02}:{:02} {}", hour_12, timestamp_local_minute, meridiem);
|
||||
|
||||
let reference_local = reference.to_offset(timezone);
|
||||
let reference_local_date = reference_local.date();
|
||||
let timestamp_local_date = timestamp_local.date();
|
||||
|
||||
if timestamp_local_date == reference_local_date {
|
||||
return formatted_time;
|
||||
}
|
||||
|
||||
if reference_local_date.previous_day() == Some(timestamp_local_date) {
|
||||
return format!("yesterday at {}", formatted_time);
|
||||
}
|
||||
|
||||
format!(
|
||||
"{:02}/{:02}/{}",
|
||||
timestamp_local_date.month() as u32,
|
||||
timestamp_local_date.day(),
|
||||
timestamp_local_date.year()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -701,6 +716,7 @@ mod tests {
|
|||
use gpui::HighlightStyle;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rich_text::Highlight;
|
||||
use time::{Date, OffsetDateTime, Time, UtcOffset};
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -749,4 +765,99 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_today() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"03:30 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 09:00 am"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_less_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_more_than_24_hours_ago() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 06:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_midnight() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 11:55 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_yesterday_over_month() {
|
||||
let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"yesterday at 08:00 pm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_before_yesterday() {
|
||||
let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
|
||||
let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
|
||||
|
||||
assert_eq!(
|
||||
format_timestamp(reference, timestamp, test_timezone()),
|
||||
"04/10/1990"
|
||||
);
|
||||
}
|
||||
|
||||
fn test_timezone() -> UtcOffset {
|
||||
UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
|
||||
}
|
||||
|
||||
fn create_offset_datetime(
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
hour: u8,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
) -> OffsetDateTime {
|
||||
let date =
|
||||
Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
|
||||
let time = Time::from_hms(hour, minute, second).unwrap();
|
||||
date.with_time(time).assume_utc() // Assume UTC for simplicity
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
|
||||
use client::UserId;
|
||||
use collections::HashMap;
|
||||
use editor::{AnchorRangeExt, Editor, EditorElement, EditorStyle};
|
||||
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
|
||||
Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, Completion,
|
||||
LanguageRegistry, LanguageServerId, ToOffset,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use project::search::SearchQuery;
|
||||
use settings::Settings;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, UiTextSize};
|
||||
|
||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
|
@ -31,6 +36,43 @@ pub struct MessageEditor {
|
|||
channel_id: Option<ChannelId>,
|
||||
}
|
||||
|
||||
struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
|
||||
|
||||
impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<Vec<language::Completion>>> {
|
||||
let Some(handle) = self.0.upgrade() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
handle.update(cx, |message_editor, cx| {
|
||||
message_editor.completions(buffer, buffer_position, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Arc<RwLock<Box<[language::Completion]>>>,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<bool>> {
|
||||
Task::ready(Ok(false))
|
||||
}
|
||||
|
||||
fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
_buffer: Model<Buffer>,
|
||||
_completion: Completion,
|
||||
_push_to_history: bool,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageEditor {
|
||||
pub fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
|
@ -38,8 +80,11 @@ impl MessageEditor {
|
|||
editor: View<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let this = cx.view().downgrade();
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_use_autoclose(false);
|
||||
editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
|
||||
});
|
||||
|
||||
let buffer = editor
|
||||
|
@ -149,6 +194,71 @@ impl MessageEditor {
|
|||
}
|
||||
}
|
||||
|
||||
fn completions(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let Some(query) = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == '@' {
|
||||
return Some(query.chars().rev().collect::<String>());
|
||||
}
|
||||
if ch.is_whitespace() || !ch.is_ascii() {
|
||||
break;
|
||||
}
|
||||
query.push(ch);
|
||||
}
|
||||
return None;
|
||||
}) else {
|
||||
return Task::ready(Ok(vec![]));
|
||||
};
|
||||
|
||||
let start_offset = end_offset - query.len();
|
||||
let start_anchor = buffer.read(cx).anchor_before(start_offset);
|
||||
|
||||
let candidates = self
|
||||
.users
|
||||
.keys()
|
||||
.map(|user| StringMatchCandidate {
|
||||
id: 0,
|
||||
string: user.clone(),
|
||||
char_bag: user.chars().collect(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
cx.spawn(|_, cx| async move {
|
||||
let matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
10,
|
||||
&Default::default(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(matches
|
||||
.into_iter()
|
||||
.map(|mat| Completion {
|
||||
old_range: start_anchor..end_anchor,
|
||||
new_text: mat.string.clone(),
|
||||
label: CodeLabel {
|
||||
filter_range: 1..mat.string.len() + 1,
|
||||
text: format!("@{}", mat.string),
|
||||
runs: Vec::new(),
|
||||
},
|
||||
documentation: None,
|
||||
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
||||
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
async fn find_mentions(
|
||||
this: WeakView<MessageEditor>,
|
||||
buffer: BufferSnapshot,
|
||||
|
@ -216,7 +326,7 @@ impl Render for MessageEditor {
|
|||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: rems(0.875).into(),
|
||||
font_size: UiTextSize::Small.rems().into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3).into(),
|
||||
|
|
|
@ -85,7 +85,14 @@ impl Render for CollabTitlebarItem {
|
|||
.gap_1()
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.child(div().pr_1().children(self.render_project_branch(cx)))
|
||||
.children(self.render_project_branch(cx)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("collaborator-list")
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.overflow_x_scroll()
|
||||
.when_some(
|
||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||
|this, ((current_user, peer_id), room)| {
|
||||
|
|
|
@ -37,8 +37,8 @@ impl RenderOnce for FacePile {
|
|||
}
|
||||
|
||||
impl ParentElement for FacePile {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.faces
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.faces.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ impl CollabNotification {
|
|||
}
|
||||
|
||||
impl ParentElement for CollabNotification {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
let action = command.action;
|
||||
cx.focus(&self.previous_focus_handle);
|
||||
cx.window_context()
|
||||
.spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
|
||||
.spawn(move |mut cx| async move { cx.update(|cx| cx.dispatch_action(action)) })
|
||||
.detach_and_log_err(cx);
|
||||
self.dismissed(cx);
|
||||
}
|
||||
|
|
|
@ -974,7 +974,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
|||
.browser_download_url;
|
||||
|
||||
let mut response = http
|
||||
.get(&url, Default::default(), true)
|
||||
.get(url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
|
|
|
@ -355,7 +355,7 @@ fn initiate_sign_in(cx: &mut WindowContext) {
|
|||
|
||||
cx.spawn(|mut cx| async move {
|
||||
task.await;
|
||||
if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
|
||||
if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
|
||||
Status::Authorized => workspace.show_toast(
|
||||
|
|
|
@ -1584,27 +1584,34 @@ mod tests {
|
|||
}
|
||||
|
||||
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
|
||||
let editor_view = editor.clone();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
snapshot
|
||||
.blocks_in_range(0..snapshot.max_point().row())
|
||||
.enumerate()
|
||||
.filter_map(|(ix, (row, block))| {
|
||||
let name = match block {
|
||||
TransformBlock::Custom(block) => block
|
||||
.render(&mut BlockContext {
|
||||
view_context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_padding: px(0.),
|
||||
gutter_width: px(0.),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
block_id: ix,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
})
|
||||
.inner_id()?
|
||||
.try_into()
|
||||
.ok()?,
|
||||
let name: SharedString = match block {
|
||||
TransformBlock::Custom(block) => cx.with_element_context({
|
||||
let editor_view = editor_view.clone();
|
||||
|cx| -> Option<SharedString> {
|
||||
block
|
||||
.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_padding: px(0.),
|
||||
gutter_width: px(0.),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
block_id: ix,
|
||||
view: editor_view,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
})
|
||||
.inner_id()?
|
||||
.try_into()
|
||||
.ok()
|
||||
}
|
||||
})?,
|
||||
|
||||
TransformBlock::ExcerptHeader {
|
||||
starts_new_buffer, ..
|
||||
|
|
|
@ -4,7 +4,7 @@ use super::{
|
|||
};
|
||||
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, Pixels, ViewContext};
|
||||
use gpui::{AnyElement, ElementContext, Pixels, View};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
|
@ -81,7 +81,8 @@ pub enum BlockStyle {
|
|||
}
|
||||
|
||||
pub struct BlockContext<'a, 'b> {
|
||||
pub view_context: &'b mut ViewContext<'a, Editor>,
|
||||
pub context: &'b mut ElementContext<'a>,
|
||||
pub view: View<Editor>,
|
||||
pub anchor_x: Pixels,
|
||||
pub gutter_width: Pixels,
|
||||
pub gutter_padding: Pixels,
|
||||
|
@ -933,16 +934,16 @@ impl BlockDisposition {
|
|||
}
|
||||
|
||||
impl<'a> Deref for BlockContext<'a, '_> {
|
||||
type Target = ViewContext<'a, Editor>;
|
||||
type Target = ElementContext<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.view_context
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BlockContext<'_, '_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.view_context
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ pub(crate) use actions::*;
|
|||
use aho_corasick::AhoCorasick;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use blink_manager::BlinkManager;
|
||||
use client::{Client, Collaborator, ParticipantIndex};
|
||||
use client::{Collaborator, ParticipantIndex};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||
use convert_case::{Case, Casing};
|
||||
|
@ -57,9 +57,10 @@ use gpui::{
|
|||
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||
DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
||||
HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
|
||||
ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels,
|
||||
Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakView,
|
||||
WhiteSpace, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
|
@ -71,8 +72,7 @@ use language::{
|
|||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
|
||||
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
|
||||
Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
|
||||
SelectionGoal, TransactionId,
|
||||
Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
|
||||
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
|
||||
|
@ -88,7 +88,7 @@ use ordered_float::OrderedFloat;
|
|||
use parking_lot::RwLock;
|
||||
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::{self, *};
|
||||
use rpc::proto::*;
|
||||
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
|
||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -365,6 +365,7 @@ pub struct Editor {
|
|||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
||||
project: Option<Model<Project>>,
|
||||
completion_provider: Option<Box<dyn CompletionProvider>>,
|
||||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||
blink_manager: Model<BlinkManager>,
|
||||
show_cursor_names: bool,
|
||||
|
@ -408,6 +409,7 @@ pub struct Editor {
|
|||
style: Option<EditorStyle>,
|
||||
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
show_copilot_suggestions: bool,
|
||||
use_autoclose: bool,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
|
@ -731,85 +733,21 @@ impl CompletionsMenu {
|
|||
return None;
|
||||
}
|
||||
|
||||
let Some(project) = editor.project.clone() else {
|
||||
let Some(provider) = editor.completion_provider.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let client = project.read(cx).client();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.matches.iter().map(|m| m.candidate_id).collect(),
|
||||
self.completions.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let is_remote = project.read(cx).is_remote();
|
||||
let project_id = project.read(cx).remote_id();
|
||||
|
||||
let completions = self.completions.clone();
|
||||
let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
|
||||
|
||||
Some(cx.spawn(move |this, mut cx| async move {
|
||||
if is_remote {
|
||||
let Some(project_id) = project_id else {
|
||||
log::error!("Remote project without remote_id");
|
||||
return;
|
||||
};
|
||||
|
||||
for completion_index in completion_indices {
|
||||
let completions_guard = completions.read();
|
||||
let completion = &completions_guard[completion_index];
|
||||
if completion.documentation.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let server_id = completion.server_id;
|
||||
let completion = completion.lsp_completion.clone();
|
||||
drop(completions_guard);
|
||||
|
||||
Self::resolve_completion_documentation_remote(
|
||||
project_id,
|
||||
server_id,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
completion,
|
||||
client.clone(),
|
||||
language_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
_ = this.update(&mut cx, |_, cx| cx.notify());
|
||||
}
|
||||
} else {
|
||||
for completion_index in completion_indices {
|
||||
let completions_guard = completions.read();
|
||||
let completion = &completions_guard[completion_index];
|
||||
if completion.documentation.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let server_id = completion.server_id;
|
||||
let completion = completion.lsp_completion.clone();
|
||||
drop(completions_guard);
|
||||
|
||||
let server = project
|
||||
.read_with(&mut cx, |project, _| {
|
||||
project.language_server_for_id(server_id)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(server) = server else {
|
||||
return;
|
||||
};
|
||||
|
||||
Self::resolve_completion_documentation_local(
|
||||
server,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
completion,
|
||||
language_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
_ = this.update(&mut cx, |_, cx| cx.notify());
|
||||
}
|
||||
return Some(cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
fn attempt_resolve_selected_completion_documentation(
|
||||
|
@ -826,146 +764,16 @@ impl CompletionsMenu {
|
|||
let Some(project) = project else {
|
||||
return;
|
||||
};
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
|
||||
let completions = self.completions.clone();
|
||||
let completions_guard = completions.read();
|
||||
let completion = &completions_guard[completion_index];
|
||||
if completion.documentation.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let server_id = completion.server_id;
|
||||
let completion = completion.lsp_completion.clone();
|
||||
drop(completions_guard);
|
||||
|
||||
if project.read(cx).is_remote() {
|
||||
let Some(project_id) = project.read(cx).remote_id() else {
|
||||
log::error!("Remote project without remote_id");
|
||||
return;
|
||||
};
|
||||
|
||||
let client = project.read(cx).client();
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
Self::resolve_completion_documentation_remote(
|
||||
project_id,
|
||||
server_id,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
completion,
|
||||
client,
|
||||
language_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
_ = this.update(&mut cx, |_, cx| cx.notify());
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
let Some(server) = project.read(cx).language_server_for_id(server_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
Self::resolve_completion_documentation_local(
|
||||
server,
|
||||
completions,
|
||||
completion_index,
|
||||
completion,
|
||||
language_registry,
|
||||
)
|
||||
.await;
|
||||
|
||||
_ = this.update(&mut cx, |_, cx| cx.notify());
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_completion_documentation_remote(
|
||||
project_id: u64,
|
||||
server_id: LanguageServerId,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
completion_index: usize,
|
||||
completion: lsp::CompletionItem,
|
||||
client: Arc<Client>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
) {
|
||||
let request = proto::ResolveCompletionDocumentation {
|
||||
project_id,
|
||||
language_server_id: server_id.0 as u64,
|
||||
lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
|
||||
};
|
||||
|
||||
let Some(response) = client
|
||||
.request(request)
|
||||
.await
|
||||
.context("completion documentation resolve proto request")
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if response.text.is_empty() {
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(Documentation::Undocumented);
|
||||
}
|
||||
|
||||
let documentation = if response.is_markdown {
|
||||
Documentation::MultiLineMarkdown(
|
||||
markdown::parse_markdown(&response.text, &language_registry, None).await,
|
||||
)
|
||||
} else if response.text.lines().count() <= 1 {
|
||||
Documentation::SingleLine(response.text)
|
||||
} else {
|
||||
Documentation::MultiLinePlainText(response.text)
|
||||
};
|
||||
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
}
|
||||
|
||||
async fn resolve_completion_documentation_local(
|
||||
server: Arc<lsp::LanguageServer>,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
completion_index: usize,
|
||||
completion: lsp::CompletionItem,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
) {
|
||||
let can_resolve = server
|
||||
.capabilities()
|
||||
.completion_provider
|
||||
.as_ref()
|
||||
.and_then(|options| options.resolve_provider)
|
||||
.unwrap_or(false);
|
||||
if !can_resolve {
|
||||
return;
|
||||
}
|
||||
|
||||
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
|
||||
let Some(completion_item) = request.await.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(lsp_documentation) = completion_item.documentation {
|
||||
let documentation = language::prepare_completion_documentation(
|
||||
&lsp_documentation,
|
||||
&language_registry,
|
||||
None, // TODO: Try to reasonably work out which language the completion is for
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
} else {
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(Documentation::Undocumented);
|
||||
}
|
||||
let resolve_task = project.update(cx, |project, cx| {
|
||||
project.resolve_completions(vec![completion_index], self.completions.clone(), cx)
|
||||
});
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn visible(&self) -> bool {
|
||||
|
@ -1574,6 +1382,7 @@ impl Editor {
|
|||
ime_transaction: Default::default(),
|
||||
active_diagnostics: None,
|
||||
soft_wrap_mode_override,
|
||||
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
||||
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
||||
project,
|
||||
blink_manager: blink_manager.clone(),
|
||||
|
@ -1603,6 +1412,7 @@ impl Editor {
|
|||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
read_only: false,
|
||||
use_autoclose: true,
|
||||
leader_peer_id: None,
|
||||
remote_id: None,
|
||||
hover_state: Default::default(),
|
||||
|
@ -1806,6 +1616,10 @@ impl Editor {
|
|||
self.collaboration_hub = Some(hub);
|
||||
}
|
||||
|
||||
pub fn set_completion_provider(&mut self, hub: Box<dyn CompletionProvider>) {
|
||||
self.completion_provider = Some(hub);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
|
@ -1880,6 +1694,10 @@ impl Editor {
|
|||
self.read_only = read_only;
|
||||
}
|
||||
|
||||
pub fn set_use_autoclose(&mut self, autoclose: bool) {
|
||||
self.use_autoclose = autoclose;
|
||||
}
|
||||
|
||||
pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
|
||||
self.show_copilot_suggestions = show_copilot_suggestions;
|
||||
}
|
||||
|
@ -2478,7 +2296,12 @@ impl Editor {
|
|||
),
|
||||
&bracket_pair.start[..prefix_len],
|
||||
));
|
||||
if following_text_allows_autoclose && preceding_text_matches_prefix {
|
||||
let autoclose = self.use_autoclose
|
||||
&& snapshot.settings_at(selection.start, cx).use_autoclose;
|
||||
if autoclose
|
||||
&& following_text_allows_autoclose
|
||||
&& preceding_text_matches_prefix
|
||||
{
|
||||
let anchor = snapshot.anchor_before(selection.end);
|
||||
new_selections.push((selection.map(|_| anchor), text.len()));
|
||||
new_autoclose_regions.push((
|
||||
|
@ -3252,9 +3075,7 @@ impl Editor {
|
|||
return;
|
||||
}
|
||||
|
||||
let project = if let Some(project) = self.project.clone() {
|
||||
project
|
||||
} else {
|
||||
let Some(provider) = self.completion_provider.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -3270,9 +3091,7 @@ impl Editor {
|
|||
};
|
||||
|
||||
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
|
||||
let completions = project.update(cx, |project, cx| {
|
||||
project.completions(&buffer, buffer_position, cx)
|
||||
});
|
||||
let completions = provider.completions(&buffer, buffer_position, cx);
|
||||
|
||||
let id = post_inc(&mut self.next_completion_id);
|
||||
let task = cx.spawn(|this, mut cx| {
|
||||
|
@ -3381,6 +3200,7 @@ impl Editor {
|
|||
let buffer_handle = completions_menu.buffer;
|
||||
let completions = completions_menu.completions.read();
|
||||
let completion = completions.get(mat.candidate_id)?;
|
||||
cx.stop_propagation();
|
||||
|
||||
let snippet;
|
||||
let text;
|
||||
|
@ -3477,15 +3297,13 @@ impl Editor {
|
|||
this.refresh_copilot_suggestions(true, cx);
|
||||
});
|
||||
|
||||
let project = self.project.clone()?;
|
||||
let apply_edits = project.update(cx, |project, cx| {
|
||||
project.apply_additional_edits_for_completion(
|
||||
buffer_handle,
|
||||
completion.clone(),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let provider = self.completion_provider.as_ref()?;
|
||||
let apply_edits = provider.apply_additional_edits_for_completion(
|
||||
buffer_handle,
|
||||
completion.clone(),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
Some(cx.foreground_executor().spawn(async move {
|
||||
apply_edits.await?;
|
||||
Ok(())
|
||||
|
@ -3572,7 +3390,7 @@ impl Editor {
|
|||
let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
|
||||
|
||||
let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
|
||||
cx.update(|_, cx| {
|
||||
cx.update(|cx| {
|
||||
entries.sort_unstable_by_key(|(buffer, _)| {
|
||||
buffer.read(cx).file().map(|f| f.path().clone())
|
||||
});
|
||||
|
@ -3907,7 +3725,7 @@ impl Editor {
|
|||
self.show_cursor_names = true;
|
||||
cx.notify();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
cx.background_executor().timer(Duration::from_secs(3)).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_cursor_names = false;
|
||||
cx.notify()
|
||||
|
@ -4094,7 +3912,7 @@ impl Editor {
|
|||
gutter_hovered: bool,
|
||||
_line_height: Pixels,
|
||||
_gutter_margin: Pixels,
|
||||
cx: &mut ViewContext<Self>,
|
||||
editor_view: View<Editor>,
|
||||
) -> Vec<Option<IconButton>> {
|
||||
fold_data
|
||||
.iter()
|
||||
|
@ -4104,14 +3922,19 @@ impl Editor {
|
|||
.map(|(fold_status, buffer_row, active)| {
|
||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
||||
IconButton::new(ix as usize, ui::IconName::ChevronDown)
|
||||
.on_click(cx.listener(move |editor, _e, cx| match fold_status {
|
||||
FoldStatus::Folded => {
|
||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||
.on_click({
|
||||
let view = editor_view.clone();
|
||||
move |_e, cx| {
|
||||
view.update(cx, |editor, cx| match fold_status {
|
||||
FoldStatus::Folded => {
|
||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||
}
|
||||
FoldStatus::Foldable => {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
FoldStatus::Foldable => {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
}
|
||||
}))
|
||||
})
|
||||
.icon_color(ui::Color::Muted)
|
||||
.icon_size(ui::IconSize::Small)
|
||||
.selected(fold_status == FoldStatus::Folded)
|
||||
|
@ -9097,6 +8920,66 @@ impl CollaborationHub for Model<Project> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait CompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: text::Anchor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Vec<Completion>>>;
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
completion_indices: Vec<usize>,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<bool>>;
|
||||
|
||||
fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
buffer: Model<Buffer>,
|
||||
completion: Completion,
|
||||
push_to_history: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>>;
|
||||
}
|
||||
|
||||
impl CompletionProvider for Model<Project> {
|
||||
fn completions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: text::Anchor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
self.update(cx, |project, cx| {
|
||||
project.completions(&buffer, buffer_position, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
completion_indices: Vec<usize>,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<bool>> {
|
||||
self.update(cx, |project, cx| {
|
||||
project.resolve_completions(completion_indices, completions, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
buffer: Model<Buffer>,
|
||||
completion: Completion,
|
||||
push_to_history: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<Result<Option<language::Transaction>>> {
|
||||
self.update(cx, |project, cx| {
|
||||
project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn inlay_hint_settings(
|
||||
location: Anchor,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
|
@ -9300,7 +9183,7 @@ impl Render for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
impl InputHandler for Editor {
|
||||
impl ViewInputHandler for Editor {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
|
@ -9697,10 +9580,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
|
|||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.visible_on_hover(group_id)
|
||||
.on_click(cx.listener({
|
||||
.on_click({
|
||||
let message = diagnostic.message.clone();
|
||||
move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
|
||||
}))
|
||||
move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
|
|
|
@ -25,12 +25,12 @@ use collections::{BTreeMap, HashMap};
|
|||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{
|
||||
div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
|
||||
AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
|
||||
CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla,
|
||||
InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta,
|
||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
|
||||
Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext,
|
||||
AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle,
|
||||
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds,
|
||||
InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
|
||||
SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
|
||||
TextStyle, View, ViewContext, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::ShowWhitespaceSetting;
|
||||
|
@ -330,7 +330,7 @@ impl EditorElement {
|
|||
register_action(view, cx, Editor::display_cursor_names);
|
||||
}
|
||||
|
||||
fn register_key_listeners(&self, cx: &mut WindowContext) {
|
||||
fn register_key_listeners(&self, cx: &mut ElementContext) {
|
||||
cx.on_key_event({
|
||||
let editor = self.editor.clone();
|
||||
move |event: &ModifiersChangedEvent, phase, cx| {
|
||||
|
@ -628,7 +628,7 @@ impl EditorElement {
|
|||
gutter_bounds: Bounds<Pixels>,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
layout: &LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let bounds = gutter_bounds.union(&text_bounds);
|
||||
let scroll_top =
|
||||
|
@ -711,7 +711,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
|
@ -782,7 +782,7 @@ impl EditorElement {
|
|||
});
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
|
||||
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
|
@ -886,7 +886,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
|
@ -1153,7 +1153,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
|
@ -1218,9 +1218,11 @@ impl EditorElement {
|
|||
popover_origin.x = popover_origin.x + x_out_of_bounds;
|
||||
}
|
||||
|
||||
cx.break_content_mask(|cx| {
|
||||
hover_popover.draw(popover_origin, available_space, cx)
|
||||
});
|
||||
if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
|
||||
cx.break_content_mask(|cx| {
|
||||
hover_popover.draw(popover_origin, available_space, cx)
|
||||
});
|
||||
}
|
||||
|
||||
current_y = popover_origin.y - HOVER_POPOVER_GAP;
|
||||
}
|
||||
|
@ -1266,7 +1268,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if layout.mode != EditorMode::Full {
|
||||
return;
|
||||
|
@ -1510,7 +1512,7 @@ impl EditorElement {
|
|||
layout: &LayoutState,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
let end_row = layout.visible_display_row_range.end;
|
||||
|
@ -1562,7 +1564,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout: &mut LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
let scroll_left = scroll_position.x * layout.position_map.em_width;
|
||||
|
@ -1812,7 +1814,7 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut WindowContext) -> LayoutState {
|
||||
fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut ElementContext) -> LayoutState {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let style = self.style.clone();
|
||||
|
@ -2081,7 +2083,9 @@ impl EditorElement {
|
|||
.width;
|
||||
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
|
||||
|
||||
let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
let editor_view = cx.view().clone();
|
||||
let (scroll_width, blocks) = cx.with_element_context(|cx| {
|
||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||
self.layout_blocks(
|
||||
start_row..end_row,
|
||||
&snapshot,
|
||||
|
@ -2095,8 +2099,10 @@ impl EditorElement {
|
|||
&style,
|
||||
&line_layouts,
|
||||
editor,
|
||||
editor_view,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let scroll_max = point(
|
||||
|
@ -2128,7 +2134,13 @@ impl EditorElement {
|
|||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||
if editor.context_menu_visible() {
|
||||
let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
|
||||
let max_height = cmp::min(
|
||||
12. * line_height,
|
||||
cmp::max(
|
||||
3. * line_height,
|
||||
(bounds.size.height - line_height) / 2.,
|
||||
)
|
||||
);
|
||||
context_menu =
|
||||
editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
|
||||
}
|
||||
|
@ -2166,15 +2178,19 @@ impl EditorElement {
|
|||
cx,
|
||||
);
|
||||
|
||||
let fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
|
||||
let editor_view = cx.view().clone();
|
||||
let fold_indicators = cx.with_element_context(|cx| {
|
||||
|
||||
cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
|
||||
editor.render_fold_indicators(
|
||||
fold_statuses,
|
||||
&style,
|
||||
editor.gutter_hovered,
|
||||
line_height,
|
||||
gutter_margin,
|
||||
cx,
|
||||
editor_view,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let invisible_symbol_font_size = font_size / 2.;
|
||||
|
@ -2265,7 +2281,8 @@ impl EditorElement {
|
|||
style: &EditorStyle,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
editor: &mut Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
editor_view: View<Editor>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (Pixels, Vec<BlockLayout>) {
|
||||
let mut block_id = 0;
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
|
@ -2279,7 +2296,7 @@ impl EditorElement {
|
|||
available_space: Size<AvailableSpace>,
|
||||
block_id: usize,
|
||||
editor: &mut Editor,
|
||||
cx: &mut ViewContext<Editor>| {
|
||||
cx: &mut ElementContext| {
|
||||
let mut element = match block {
|
||||
TransformBlock::Custom(block) => {
|
||||
let align_to = block
|
||||
|
@ -2298,13 +2315,14 @@ impl EditorElement {
|
|||
};
|
||||
|
||||
block.render(&mut BlockContext {
|
||||
view_context: cx,
|
||||
context: cx,
|
||||
anchor_x,
|
||||
gutter_padding,
|
||||
line_height,
|
||||
gutter_width,
|
||||
em_width,
|
||||
block_id,
|
||||
view: editor_view.clone(),
|
||||
editor_style: &self.style,
|
||||
})
|
||||
}
|
||||
|
@ -2496,7 +2514,7 @@ impl EditorElement {
|
|||
&mut self,
|
||||
interactive_bounds: &InteractiveBounds,
|
||||
layout: &LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
|
@ -2556,7 +2574,7 @@ impl EditorElement {
|
|||
gutter_bounds: Bounds<Pixels>,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
layout: &LayoutState,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let interactive_bounds = InteractiveBounds {
|
||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||
|
@ -2779,7 +2797,7 @@ impl LineWithInvisibles {
|
|||
content_origin: gpui::Point<Pixels>,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
selection_ranges: &[Range<DisplayPoint>],
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
|
||||
|
@ -2813,7 +2831,7 @@ impl LineWithInvisibles {
|
|||
row: u32,
|
||||
line_height: Pixels,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let allowed_invisibles_regions = match whitespace_setting {
|
||||
ShowWhitespaceSetting::None => return,
|
||||
|
@ -2862,7 +2880,7 @@ impl Element for EditorElement {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_element_state: Option<Self::State>,
|
||||
cx: &mut gpui::WindowContext,
|
||||
cx: &mut gpui::ElementContext,
|
||||
) -> (gpui::LayoutId, Self::State) {
|
||||
cx.with_view_id(self.editor.entity_id(), |cx| {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
|
@ -2874,34 +2892,36 @@ impl Element for EditorElement {
|
|||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
|
||||
cx.request_layout(&style, None)
|
||||
cx.with_element_context(|cx| cx.request_layout(&style, None))
|
||||
}
|
||||
EditorMode::AutoHeight { max_lines } => {
|
||||
let editor_handle = cx.view().clone();
|
||||
let max_line_number_width =
|
||||
self.max_line_number_width(&editor.snapshot(cx), cx);
|
||||
cx.request_measured_layout(
|
||||
Style::default(),
|
||||
move |known_dimensions, _, cx| {
|
||||
editor_handle
|
||||
.update(cx, |editor, cx| {
|
||||
compute_auto_height_layout(
|
||||
editor,
|
||||
max_lines,
|
||||
max_line_number_width,
|
||||
known_dimensions,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
},
|
||||
)
|
||||
cx.with_element_context(|cx| {
|
||||
cx.request_measured_layout(
|
||||
Style::default(),
|
||||
move |known_dimensions, _, cx| {
|
||||
editor_handle
|
||||
.update(cx, |editor, cx| {
|
||||
compute_auto_height_layout(
|
||||
editor,
|
||||
max_lines,
|
||||
max_line_number_width,
|
||||
known_dimensions,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
EditorMode::Full => {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
cx.request_layout(&style, None)
|
||||
cx.with_element_context(|cx| cx.request_layout(&style, None))
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2914,7 +2934,7 @@ impl Element for EditorElement {
|
|||
&mut self,
|
||||
bounds: Bounds<gpui::Pixels>,
|
||||
_element_state: &mut Self::State,
|
||||
cx: &mut gpui::WindowContext,
|
||||
cx: &mut gpui::ElementContext,
|
||||
) {
|
||||
let editor = self.editor.clone();
|
||||
|
||||
|
@ -2943,9 +2963,10 @@ impl Element for EditorElement {
|
|||
self.register_key_listeners(cx);
|
||||
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let input_handler =
|
||||
ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
||||
cx.handle_input(&focus_handle, input_handler);
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
ElementInputHandler::new(bounds, self.editor.clone()),
|
||||
);
|
||||
|
||||
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
|
||||
if layout.gutter_size.width > Pixels::ZERO {
|
||||
|
@ -3195,7 +3216,7 @@ impl Cursor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
|
||||
pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
|
||||
let bounds = match self.shape {
|
||||
CursorShape::Bar => Bounds {
|
||||
origin: self.origin + origin,
|
||||
|
@ -3275,7 +3296,7 @@ pub struct HighlightedRangeLine {
|
|||
}
|
||||
|
||||
impl HighlightedRange {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
|
||||
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
|
||||
self.paint_lines(
|
||||
|
@ -3294,7 +3315,7 @@ impl HighlightedRange {
|
|||
start_y: Pixels,
|
||||
lines: &[HighlightedRangeLine],
|
||||
_bounds: Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
|
@ -3512,14 +3533,16 @@ mod tests {
|
|||
.unwrap();
|
||||
let state = cx
|
||||
.update_window(window.into(), |view, cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -3606,14 +3629,16 @@ mod tests {
|
|||
|
||||
let state = cx
|
||||
.update_window(window.into(), |view, cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -3670,14 +3695,16 @@ mod tests {
|
|||
let mut element = EditorElement::new(&editor, style);
|
||||
let state = cx
|
||||
.update_window(window.into(), |view, cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_view_id(view.entity_id(), |cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -3695,8 +3722,10 @@ mod tests {
|
|||
|
||||
// Don't panic.
|
||||
let bounds = Bounds::<Pixels>::new(Default::default(), size);
|
||||
cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx))
|
||||
.unwrap()
|
||||
cx.update_window(window.into(), |_, cx| {
|
||||
cx.with_element_context(|cx| element.paint(bounds, &mut (), cx))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -3871,13 +3900,15 @@ mod tests {
|
|||
.unwrap();
|
||||
let layout_state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
cx.with_element_context(|cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -247,7 +247,7 @@ fn show_hover(
|
|||
};
|
||||
|
||||
// query the LSP for hover info
|
||||
let hover_request = cx.update(|_, cx| {
|
||||
let hover_request = cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.hover(&buffer, buffer_position, cx)
|
||||
})
|
||||
|
|
|
@ -72,7 +72,7 @@ impl GitRepository for LibGitRepository {
|
|||
// This check is required because index.get_path() unwraps internally :(
|
||||
check_path_to_repo_path_errors(relative_file_path)?;
|
||||
|
||||
let oid = match index.get_path(&relative_file_path, STAGE_NORMAL) {
|
||||
let oid = match index.get_path(relative_file_path, STAGE_NORMAL) {
|
||||
Some(entry) => entry.id,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
@ -81,7 +81,7 @@ impl GitRepository for LibGitRepository {
|
|||
Ok(Some(String::from_utf8(content)?))
|
||||
}
|
||||
|
||||
match logic(&self, relative_file_path) {
|
||||
match logic(self, relative_file_path) {
|
||||
Ok(value) => return value,
|
||||
Err(err) => log::error!("Error loading head text: {:?}", err),
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ impl GitRepository for LibGitRepository {
|
|||
|
||||
fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool {
|
||||
if let Some(index) = repo.index().log_err() {
|
||||
if let Some(entry) = index.get_path(&path, 0) {
|
||||
if let Some(entry) = index.get_path(path, 0) {
|
||||
if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() {
|
||||
if entry.mtime.seconds() == mtime.as_secs() as i32
|
||||
&& entry.mtime.nanoseconds() == mtime.subsec_nanos()
|
||||
|
|
|
@ -165,7 +165,7 @@ impl BufferDiff {
|
|||
let mut tree = SumTree::new();
|
||||
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
let patch = Self::diff(&diff_base, &buffer_text);
|
||||
let patch = Self::diff(diff_base, &buffer_text);
|
||||
|
||||
if let Some(patch) = patch {
|
||||
let mut divergence = 0;
|
||||
|
|
|
@ -40,14 +40,25 @@ use std::any::{Any, TypeId};
|
|||
/// register_action!(Paste);
|
||||
/// ```
|
||||
pub trait Action: 'static {
|
||||
/// Clone the action into a new box
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
|
||||
/// Cast the action to the any type
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Do a partial equality check on this action and the other
|
||||
fn partial_eq(&self, action: &dyn Action) -> bool;
|
||||
|
||||
/// Get the name of this action, for displaying in UI
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Get the name of this action for debugging
|
||||
fn debug_name() -> &'static str
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Build this action from a JSON value. This is used to construct actions from the keymap.
|
||||
/// A value of `{}` will be passed for actions that don't have any parameters.
|
||||
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
@ -62,6 +73,7 @@ impl std::fmt::Debug for dyn Action {
|
|||
}
|
||||
|
||||
impl dyn Action {
|
||||
/// Get the type id of this action
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
self.as_any().type_id()
|
||||
}
|
||||
|
@ -170,6 +182,7 @@ impl ActionRegistry {
|
|||
macro_rules! actions {
|
||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||
$(
|
||||
/// The `$name` action see [`gpui::actions!`]
|
||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)]
|
||||
#[serde(crate = "gpui::private::serde")]
|
||||
pub struct $name;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
mod async_context;
|
||||
mod entity_map;
|
||||
mod model_context;
|
||||
|
|
|
@ -213,7 +213,12 @@ impl AsyncWindowContext {
|
|||
}
|
||||
|
||||
/// A convenience method for [WindowContext::update()]
|
||||
pub fn update<R>(
|
||||
pub fn update<R>(&mut self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
|
||||
self.app.update_window(self.window, |_, cx| update(cx))
|
||||
}
|
||||
|
||||
/// A convenience method for [WindowContext::update()]
|
||||
pub fn update_root<R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
||||
) -> Result<R> {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
use crate::{
|
||||
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
||||
|
@ -352,7 +350,7 @@ impl TestAppContext {
|
|||
}
|
||||
|
||||
/// Returns the `TestWindow` backing the given handle.
|
||||
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
|
||||
pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.windows
|
||||
|
@ -642,8 +640,11 @@ impl<'a> VisualTestContext {
|
|||
.as_ref()
|
||||
.expect("Can't draw to this window without a root view")
|
||||
.entity_id();
|
||||
cx.with_view_id(entity_id, |cx| {
|
||||
f(cx).draw(origin, space, cx);
|
||||
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_view_id(entity_id, |cx| {
|
||||
f(cx).draw(origin, space, cx);
|
||||
})
|
||||
});
|
||||
|
||||
cx.refresh();
|
||||
|
|
|
@ -8,8 +8,12 @@ use std::{
|
|||
sync::atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
};
|
||||
|
||||
/// A source of assets for this app to use.
|
||||
pub trait AssetSource: 'static + Send + Sync {
|
||||
/// Load the given asset from the source path.
|
||||
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
||||
|
||||
/// List the assets at the given path.
|
||||
fn list(&self, path: &str) -> Result<Vec<SharedString>>;
|
||||
}
|
||||
|
||||
|
@ -26,15 +30,19 @@ impl AssetSource for () {
|
|||
}
|
||||
}
|
||||
|
||||
/// A unique identifier for the image cache
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ImageId(usize);
|
||||
|
||||
/// A cached and processed image.
|
||||
pub struct ImageData {
|
||||
/// The ID associated with this image
|
||||
pub id: ImageId,
|
||||
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
/// Create a new image from the given data.
|
||||
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
|
@ -44,10 +52,12 @@ impl ImageData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert this image into a byte slice.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Get the size of this image, in pixels
|
||||
pub fn size(&self) -> Size<DevicePixels> {
|
||||
let (width, height) = self.data.dimensions();
|
||||
size(width.into(), height.into())
|
||||
|
|
|
@ -2,6 +2,7 @@ use anyhow::bail;
|
|||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||
use std::fmt;
|
||||
|
||||
/// Convert an RGB hex color code number to a color type
|
||||
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
||||
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
|
||||
|
@ -9,6 +10,7 @@ pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
|||
Rgba { r, g, b, a: 1.0 }.into()
|
||||
}
|
||||
|
||||
/// Convert an RGBA hex color code number to [`Rgba`]
|
||||
pub fn rgba(hex: u32) -> Rgba {
|
||||
let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
|
||||
let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||
|
@ -17,11 +19,16 @@ pub fn rgba(hex: u32) -> Rgba {
|
|||
Rgba { r, g, b, a }
|
||||
}
|
||||
|
||||
/// An RGBA color
|
||||
#[derive(PartialEq, Clone, Copy, Default)]
|
||||
pub struct Rgba {
|
||||
/// The red component of the color, in the range 0.0 to 1.0
|
||||
pub r: f32,
|
||||
/// The green component of the color, in the range 0.0 to 1.0
|
||||
pub g: f32,
|
||||
/// The blue component of the color, in the range 0.0 to 1.0
|
||||
pub b: f32,
|
||||
/// The alpha component of the color, in the range 0.0 to 1.0
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
|
@ -32,6 +39,8 @@ impl fmt::Debug for Rgba {
|
|||
}
|
||||
|
||||
impl Rgba {
|
||||
/// Create a new [`Rgba`] color by blending this and another color together
|
||||
/// TODO!(docs): find the source for this algorithm
|
||||
pub fn blend(&self, other: Rgba) -> Self {
|
||||
if other.a >= 1.0 {
|
||||
other
|
||||
|
@ -165,12 +174,20 @@ impl TryFrom<&'_ str> for Rgba {
|
|||
}
|
||||
}
|
||||
|
||||
/// An HSLA color
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Hsla {
|
||||
/// Hue, in a range from 0 to 1
|
||||
pub h: f32,
|
||||
|
||||
/// Saturation, in a range from 0 to 1
|
||||
pub s: f32,
|
||||
|
||||
/// Lightness, in a range from 0 to 1
|
||||
pub l: f32,
|
||||
|
||||
/// Alpha, in a range from 0 to 1
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
|
@ -203,38 +220,9 @@ impl Ord for Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
pub fn to_rgb(self) -> Rgba {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn red() -> Self {
|
||||
red()
|
||||
}
|
||||
|
||||
pub fn green() -> Self {
|
||||
green()
|
||||
}
|
||||
|
||||
pub fn blue() -> Self {
|
||||
blue()
|
||||
}
|
||||
|
||||
pub fn black() -> Self {
|
||||
black()
|
||||
}
|
||||
|
||||
pub fn white() -> Self {
|
||||
white()
|
||||
}
|
||||
|
||||
pub fn transparent_black() -> Self {
|
||||
transparent_black()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Hsla {}
|
||||
|
||||
/// Construct an [`Hsla`] object from plain values
|
||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||
Hsla {
|
||||
h: h.clamp(0., 1.),
|
||||
|
@ -244,6 +232,7 @@ pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// Pure black in [`Hsla`]
|
||||
pub fn black() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
|
@ -253,6 +242,7 @@ pub fn black() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// Transparent black in [`Hsla`]
|
||||
pub fn transparent_black() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
|
@ -262,6 +252,7 @@ pub fn transparent_black() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// Pure white in [`Hsla`]
|
||||
pub fn white() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
|
@ -271,6 +262,7 @@ pub fn white() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// The color red in [`Hsla`]
|
||||
pub fn red() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
|
@ -280,6 +272,7 @@ pub fn red() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// The color blue in [`Hsla`]
|
||||
pub fn blue() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.6,
|
||||
|
@ -289,6 +282,7 @@ pub fn blue() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// The color green in [`Hsla`]
|
||||
pub fn green() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.33,
|
||||
|
@ -298,6 +292,7 @@ pub fn green() -> Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// The color yellow in [`Hsla`]
|
||||
pub fn yellow() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.16,
|
||||
|
@ -308,6 +303,41 @@ pub fn yellow() -> Hsla {
|
|||
}
|
||||
|
||||
impl Hsla {
|
||||
/// Converts this HSLA color to an RGBA color.
|
||||
pub fn to_rgb(self) -> Rgba {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// The color red
|
||||
pub fn red() -> Self {
|
||||
red()
|
||||
}
|
||||
|
||||
/// The color green
|
||||
pub fn green() -> Self {
|
||||
green()
|
||||
}
|
||||
|
||||
/// The color blue
|
||||
pub fn blue() -> Self {
|
||||
blue()
|
||||
}
|
||||
|
||||
/// The color black
|
||||
pub fn black() -> Self {
|
||||
black()
|
||||
}
|
||||
|
||||
/// The color white
|
||||
pub fn white() -> Self {
|
||||
white()
|
||||
}
|
||||
|
||||
/// The color transparent black
|
||||
pub fn transparent_black() -> Self {
|
||||
transparent_black()
|
||||
}
|
||||
|
||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
self.a == 0.0
|
||||
|
@ -339,6 +369,7 @@ impl Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new HSLA color with the same hue, and lightness, but with no saturation.
|
||||
pub fn grayscale(&self) -> Self {
|
||||
Hsla {
|
||||
h: self.h,
|
||||
|
|
|
@ -1,26 +1,69 @@
|
|||
//! Elements are the workhorses of GPUI. They are responsible for laying out and painting all of
|
||||
//! the contents of a window. Elements form a tree and are laid out according to the web layout
|
||||
//! standards as implemented by [taffy](https://github.com/DioxusLabs/taffy). Most of the time,
|
||||
//! you won't need to interact with this module or these APIs directly. Elements provide their
|
||||
//! own APIs and GPUI, or other element implementation, uses the APIs in this module to convert
|
||||
//! that element tree into the pixels you see on the screen.
|
||||
//!
|
||||
//! # Element Basics
|
||||
//!
|
||||
//! Elements are constructed by calling [`Render::render()`] on the root view of the window, which
|
||||
//! which recursively constructs the element tree from the current state of the application,.
|
||||
//! These elements are then laid out by Taffy, and painted to the screen according to their own
|
||||
//! implementation of [`Element::paint()`]. Before the start of the next frame, the entire element
|
||||
//! tree and any callbacks they have registered with GPUI are dropped and the process repeats.
|
||||
//!
|
||||
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
|
||||
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
|
||||
//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
|
||||
//! appears in the same place relative to other views and ElementIds in the frame, then the previous
|
||||
//! frame's state will be passed to the element's layout and paint methods.
|
||||
//!
|
||||
//! # Implementing your own elements
|
||||
//!
|
||||
//! Elements are intended to be the low level, imperative API to GPUI. They are responsible for upholding,
|
||||
//! or breaking, GPUI's features as they deem necessary. As an example, most GPUI elements are expected
|
||||
//! to stay in the bounds that their parent element gives them. But with [`WindowContext::break_content_mask`],
|
||||
//! you can ignore this restriction and paint anywhere inside of the window's bounds. This is useful for overlays
|
||||
//! and popups and anything else that shows up 'on top' of other elements.
|
||||
//! With great power, comes great responsibility.
|
||||
//!
|
||||
//! However, most of the time, you won't need to implement your own elements. GPUI provides a number of
|
||||
//! elements that should cover most common use cases out of the box and it's recommended that you use those
|
||||
//! to construct `components`, using the [`RenderOnce`] trait and the `#[derive(IntoElement)]` macro. Only implement
|
||||
//! elements when you need to take manual control of the layout and painting process, such as when using
|
||||
//! your own custom layout algorithm or rendering a code editor.
|
||||
|
||||
use crate::{
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId,
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
|
||||
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug};
|
||||
use std::{any::Any, fmt::Debug, ops::DerefMut};
|
||||
|
||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules.
|
||||
/// Rather than calling methods on implementers of this trait directly, you'll usually call `into_any` to convert them into an AnyElement, which manages state internally.
|
||||
/// You can create custom elements by implementing this trait.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||
/// You can create custom elements by implementing this trait, see the module-level documentation
|
||||
/// for more details.
|
||||
pub trait Element: 'static + IntoElement {
|
||||
/// The type of state to store for this element between frames. See the module-level documentation
|
||||
/// for details.
|
||||
type State: 'static;
|
||||
|
||||
/// Before an element can be painted, we need to know where it's going to be and how big it is.
|
||||
/// Use this method to request a layout from Taffy and initialize the element's state.
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State);
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
|
||||
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
||||
/// The state argument is the same state that was returned from [`Element::request_layout()`].
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
|
||||
|
||||
/// Convert this element into a dynamically-typed [`AnyElement`].
|
||||
fn into_any(self) -> AnyElement {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
|
@ -29,6 +72,7 @@ pub trait Element: 'static + IntoElement {
|
|||
/// Implemented by any type that can be converted into an element.
|
||||
pub trait IntoElement: Sized {
|
||||
/// The specific type of element into which the implementing type is converted.
|
||||
/// Useful for converting other types into elements automatically, like Strings
|
||||
type Element: Element;
|
||||
|
||||
/// The [`ElementId`] of self once converted into an [`Element`].
|
||||
|
@ -51,8 +95,8 @@ pub trait IntoElement: Sized {
|
|||
self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<T>,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
|
||||
) -> R
|
||||
where
|
||||
T: Clone + Default + Debug + Into<AvailableSpace>,
|
||||
|
@ -81,7 +125,10 @@ pub trait IntoElement: Sized {
|
|||
|
||||
impl<T: IntoElement> FluentBuilder for T {}
|
||||
|
||||
/// An object that can be drawn to the screen. This is the trait that distinguishes `Views` from
|
||||
/// models. Views are drawn to the screen and care about the current window's state, models are not and do not.
|
||||
pub trait Render: 'static + Sized {
|
||||
/// Render this view into an element tree.
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
|
||||
}
|
||||
|
||||
|
@ -92,35 +139,49 @@ impl Render for () {
|
|||
}
|
||||
|
||||
/// You can derive [`IntoElement`] on any type that implements this trait.
|
||||
/// It is used to allow views to be expressed in terms of abstract data.
|
||||
/// It is used to construct reusable `components` out of plain data. Think of
|
||||
/// components as a recipe for a certain pattern of elements. RenderOnce allows
|
||||
/// you to invoke this pattern, without breaking the fluent builder pattern of
|
||||
/// the element APIs.
|
||||
pub trait RenderOnce: 'static {
|
||||
/// Render this component into an element tree. Note that this method
|
||||
/// takes ownership of self, as compared to [`Render::render()`] method
|
||||
/// which takes a mutable reference.
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement;
|
||||
}
|
||||
|
||||
/// This is a helper trait to provide a uniform interface for constructing elements that
|
||||
/// can accept any number of any kind of child elements
|
||||
pub trait ParentElement {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
|
||||
/// Extend this element's children with the given child elements.
|
||||
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>);
|
||||
|
||||
/// Add a single child element to this element.
|
||||
fn child(mut self, child: impl IntoElement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut().push(child.into_element().into_any());
|
||||
self.extend(std::iter::once(child.into_element().into_any()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple child elements to this element.
|
||||
fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.children_mut()
|
||||
.extend(children.into_iter().map(|child| child.into_any_element()));
|
||||
self.extend(children.into_iter().map(|child| child.into_any_element()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An element for rendering components. An implementation detail of the [`IntoElement`] derive macro
|
||||
/// for [`RenderOnce`]
|
||||
#[doc(hidden)]
|
||||
pub struct Component<C: RenderOnce>(Option<C>);
|
||||
|
||||
impl<C: RenderOnce> Component<C> {
|
||||
/// Create a new component from the given RenderOnce type.
|
||||
pub fn new(component: C) -> Self {
|
||||
Component(Some(component))
|
||||
}
|
||||
|
@ -132,14 +193,19 @@ impl<C: RenderOnce> Element for Component<C> {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let mut element = self.0.take().unwrap().render(cx).into_any_element();
|
||||
let mut element = self
|
||||
.0
|
||||
.take()
|
||||
.unwrap()
|
||||
.render(cx.deref_mut())
|
||||
.into_any_element();
|
||||
let layout_id = element.request_layout(cx);
|
||||
(layout_id, element)
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
element.paint(cx)
|
||||
}
|
||||
}
|
||||
|
@ -156,31 +222,33 @@ impl<C: RenderOnce> IntoElement for Component<C> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A globally unique identifier for an element, used to track state across frames.
|
||||
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||
|
||||
trait ElementObject {
|
||||
fn element_id(&self) -> Option<ElementId>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId;
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||
|
||||
fn paint(&mut self, cx: &mut WindowContext);
|
||||
fn paint(&mut self, cx: &mut ElementContext);
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels>;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
);
|
||||
}
|
||||
|
||||
pub struct DrawableElement<E: Element> {
|
||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||
pub(crate) struct DrawableElement<E: Element> {
|
||||
element: Option<E>,
|
||||
phase: ElementDrawPhase<E::State>,
|
||||
}
|
||||
|
@ -213,7 +281,7 @@ impl<E: Element> DrawableElement<E> {
|
|||
self.element.as_ref()?.element_id()
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
|
||||
{
|
||||
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
||||
|
@ -235,7 +303,7 @@ impl<E: Element> DrawableElement<E> {
|
|||
layout_id
|
||||
}
|
||||
|
||||
fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
|
||||
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
|
||||
match self.phase {
|
||||
ElementDrawPhase::LayoutRequested {
|
||||
layout_id,
|
||||
|
@ -280,7 +348,7 @@ impl<E: Element> DrawableElement<E> {
|
|||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels> {
|
||||
if matches!(&self.phase, ElementDrawPhase::Start) {
|
||||
self.request_layout(cx);
|
||||
|
@ -321,7 +389,7 @@ impl<E: Element> DrawableElement<E> {
|
|||
mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<E::State> {
|
||||
self.measure(available_space, cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
||||
|
@ -337,18 +405,18 @@ where
|
|||
self.as_ref().unwrap().element_id()
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, cx: &mut ElementContext) {
|
||||
DrawableElement::paint(self.take().unwrap(), cx);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels> {
|
||||
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
|
||||
}
|
||||
|
@ -357,16 +425,17 @@ where
|
|||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamically typed element that can be used to store any element type.
|
||||
pub struct AnyElement(ArenaBox<dyn ElementObject>);
|
||||
|
||||
impl AnyElement {
|
||||
pub fn new<E>(element: E) -> Self
|
||||
pub(crate) fn new<E>(element: E) -> Self
|
||||
where
|
||||
E: 'static + Element,
|
||||
E::State: Any,
|
||||
|
@ -377,11 +446,14 @@ impl AnyElement {
|
|||
AnyElement(element)
|
||||
}
|
||||
|
||||
pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
/// Request the layout ID of the element stored in this `AnyElement`.
|
||||
/// Used for laying out child elements in a parent element.
|
||||
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
self.0.request_layout(cx)
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, cx: &mut WindowContext) {
|
||||
/// Paints the element stored in this `AnyElement`.
|
||||
pub fn paint(&mut self, cx: &mut ElementContext) {
|
||||
self.0.paint(cx)
|
||||
}
|
||||
|
||||
|
@ -389,7 +461,7 @@ impl AnyElement {
|
|||
pub fn measure(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels> {
|
||||
self.0.measure(available_space, cx)
|
||||
}
|
||||
|
@ -399,11 +471,12 @@ impl AnyElement {
|
|||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.0.draw(origin, available_space, cx)
|
||||
}
|
||||
|
||||
/// Returns the element ID of the element stored in this `AnyElement`, if any.
|
||||
pub fn inner_id(&self) -> Option<ElementId> {
|
||||
self.0.element_id()
|
||||
}
|
||||
|
@ -415,13 +488,13 @@ impl Element for AnyElement {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let layout_id = self.request_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
|
||||
self.paint(cx)
|
||||
}
|
||||
}
|
||||
|
@ -463,7 +536,7 @@ impl Element for () {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
(cx.request_layout(&crate::Style::default(), None), ())
|
||||
}
|
||||
|
@ -472,7 +545,7 @@ impl Element for () {
|
|||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::State,
|
||||
_cx: &mut WindowContext,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
use refineable::Refineable as _;
|
||||
|
||||
use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
|
||||
use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled};
|
||||
|
||||
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
|
||||
/// Construct a canvas element with the given paint callback.
|
||||
/// Useful for adding short term custom drawing to a view.
|
||||
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementContext)) -> Canvas {
|
||||
Canvas {
|
||||
paint_callback: Some(Box::new(callback)),
|
||||
style: StyleRefinement::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||
/// custom element
|
||||
pub struct Canvas {
|
||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>>,
|
||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
|
@ -32,7 +36,7 @@ impl Element for Canvas {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
|
@ -40,7 +44,7 @@ impl Element for Canvas {
|
|||
(layout_id, style)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
|
||||
style.paint(bounds, cx, |cx| {
|
||||
(self.paint_callback.take().unwrap())(&bounds, cx)
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,19 +1,23 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
|
||||
point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
|
||||
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
StyleRefinement, Styled,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use media::core_video::CVImageBuffer;
|
||||
use util::ResultExt;
|
||||
|
||||
/// A source of image content.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ImageSource {
|
||||
/// Image content will be loaded from provided URI at render time.
|
||||
Uri(SharedUrl),
|
||||
/// Cached image data
|
||||
Data(Arc<ImageData>),
|
||||
// TODO: move surface definitions into mac platform module
|
||||
/// A CoreVideo image buffer
|
||||
Surface(CVImageBuffer),
|
||||
}
|
||||
|
||||
|
@ -47,12 +51,14 @@ impl From<CVImageBuffer> for ImageSource {
|
|||
}
|
||||
}
|
||||
|
||||
/// An image element.
|
||||
pub struct Img {
|
||||
interactivity: Interactivity,
|
||||
source: ImageSource,
|
||||
grayscale: bool,
|
||||
}
|
||||
|
||||
/// Create a new image element.
|
||||
pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||
Img {
|
||||
interactivity: Interactivity::default(),
|
||||
|
@ -62,6 +68,7 @@ pub fn img(source: impl Into<ImageSource>) -> Img {
|
|||
}
|
||||
|
||||
impl Img {
|
||||
/// Set the image to be displayed in grayscale.
|
||||
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||
self.grayscale = grayscale;
|
||||
self
|
||||
|
@ -74,7 +81,7 @@ impl Element for Img {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
self.interactivity
|
||||
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
||||
|
@ -84,7 +91,7 @@ impl Element for Img {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let source = self.source.clone();
|
||||
self.interactivity.paint(
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
//! A list element that can be used to render a large number of differently sized elements
|
||||
//! efficiently. Clients of this API need to ensure that elements outside of the scrolled
|
||||
//! area do not change their height for this element to function correctly. In order to minimize
|
||||
//! re-renders, this element's state is stored intrusively on your own views, so that your code
|
||||
//! can coordinate directly with the list element's cached state.
|
||||
//!
|
||||
//! If all of your elements are the same height, see [`UniformList`] for a simpler API
|
||||
|
||||
use crate::{
|
||||
point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||
DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
point, px, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Element,
|
||||
IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
|
||||
WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
||||
/// Construct a new list element
|
||||
pub fn list(state: ListState) -> List {
|
||||
List {
|
||||
state,
|
||||
|
@ -15,11 +24,13 @@ pub fn list(state: ListState) -> List {
|
|||
}
|
||||
}
|
||||
|
||||
/// A list element
|
||||
pub struct List {
|
||||
state: ListState,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
/// The list state that views must hold on behalf of the list element.
|
||||
#[derive(Clone)]
|
||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||
|
||||
|
@ -35,15 +46,24 @@ struct StateInner {
|
|||
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
||||
}
|
||||
|
||||
/// Whether the list is scrolling from top to bottom or bottom to top.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ListAlignment {
|
||||
/// The list is scrolling from top to bottom, like most lists.
|
||||
Top,
|
||||
/// The list is scrolling from bottom to top, like a chat log.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// A scroll event that has been converted to be in terms of the list's items.
|
||||
pub struct ListScrollEvent {
|
||||
/// The range of items currently visible in the list, after applying the scroll event.
|
||||
pub visible_range: Range<usize>,
|
||||
|
||||
/// The number of items that are currently visible in the list, after applying the scroll event.
|
||||
pub count: usize,
|
||||
|
||||
/// Whether the list has been scrolled.
|
||||
pub is_scrolled: bool,
|
||||
}
|
||||
|
||||
|
@ -74,6 +94,11 @@ struct UnrenderedCount(usize);
|
|||
struct Height(Pixels);
|
||||
|
||||
impl ListState {
|
||||
/// Construct a new list state, for storage on a view.
|
||||
///
|
||||
/// the overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. This can help ensure that the list
|
||||
/// doesn't flicker or pop in when scrolling.
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: ListAlignment,
|
||||
|
@ -111,10 +136,13 @@ impl ListState {
|
|||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
}
|
||||
|
||||
/// The number of items in this list.
|
||||
pub fn item_count(&self) -> usize {
|
||||
self.0.borrow().items.summary().count
|
||||
}
|
||||
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// by `count` new items that must be recalculated.
|
||||
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
|
@ -141,6 +169,7 @@ impl ListState {
|
|||
state.items = new_heights;
|
||||
}
|
||||
|
||||
/// Set a handler that will be called when the list is scrolled.
|
||||
pub fn set_scroll_handler(
|
||||
&self,
|
||||
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||
|
@ -148,10 +177,12 @@ impl ListState {
|
|||
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||
}
|
||||
|
||||
/// Get the current scroll offset, in terms of the list's items.
|
||||
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||
self.0.borrow().logical_scroll_top()
|
||||
}
|
||||
|
||||
/// Scroll the list to the given offset
|
||||
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
let item_count = state.items.summary().count;
|
||||
|
@ -163,6 +194,7 @@ impl ListState {
|
|||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item, such that the item is fully visible.
|
||||
pub fn scroll_to_reveal_item(&self, ix: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
|
@ -193,7 +225,8 @@ impl ListState {
|
|||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
|
||||
/// Get the bounds for the given item in window coordinates.
|
||||
/// Get the bounds for the given item in window coordinates, if it's
|
||||
/// been rendered.
|
||||
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
||||
let state = &*self.0.borrow();
|
||||
|
||||
|
@ -310,9 +343,13 @@ impl std::fmt::Debug for ListItem {
|
|||
}
|
||||
}
|
||||
|
||||
/// An offset into the list's items, in terms of the item index and the number
|
||||
/// of pixels off the top left of the item.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ListOffset {
|
||||
/// The index of an item in the list
|
||||
pub item_ix: usize,
|
||||
/// The number of pixels to offset from the item index.
|
||||
pub offset_in_item: Pixels,
|
||||
}
|
||||
|
||||
|
@ -322,7 +359,7 @@ impl Element for List {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut crate::WindowContext,
|
||||
cx: &mut crate::ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
|
@ -336,7 +373,7 @@ impl Element for List {
|
|||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
_state: &mut Self::State,
|
||||
cx: &mut crate::WindowContext,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
|
||||
|
|
|
@ -2,14 +2,17 @@ use smallvec::SmallVec;
|
|||
use taffy::style::{Display, Position};
|
||||
|
||||
use crate::{
|
||||
point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
|
||||
Point, Size, Style, WindowContext,
|
||||
point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement,
|
||||
Pixels, Point, Size, Style,
|
||||
};
|
||||
|
||||
/// The state that the overlay element uses to track its children.
|
||||
pub struct OverlayState {
|
||||
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
||||
}
|
||||
|
||||
/// An overlay element that can be used to display UI that
|
||||
/// floats on top of other UI elements.
|
||||
pub struct Overlay {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
anchor_corner: AnchorCorner,
|
||||
|
@ -60,8 +63,8 @@ impl Overlay {
|
|||
}
|
||||
|
||||
impl ParentElement for Overlay {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +74,7 @@ impl Element for Overlay {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
let child_layout_ids = self
|
||||
.children
|
||||
|
@ -94,7 +97,7 @@ impl Element for Overlay {
|
|||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if element_state.child_layout_ids.is_empty() {
|
||||
return;
|
||||
|
@ -191,15 +194,21 @@ enum Axis {
|
|||
Vertical,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when fitting the overlay to be inside the window.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum OverlayFitMode {
|
||||
/// Snap the overlay to the window edge
|
||||
SnapToWindow,
|
||||
/// Switch which corner anchor this overlay is attached to
|
||||
SwitchAnchor,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when positioning the overlay.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum OverlayPositionMode {
|
||||
/// Position the overlay relative to the window
|
||||
Window,
|
||||
/// Position the overlay relative to its parent
|
||||
Local,
|
||||
}
|
||||
|
||||
|
@ -226,11 +235,16 @@ impl OverlayPositionMode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Which corner of the overlay should be considered the anchor.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AnchorCorner {
|
||||
/// The top left corner
|
||||
TopLeft,
|
||||
/// The top right corner
|
||||
TopRight,
|
||||
/// The bottom left corner
|
||||
BottomLeft,
|
||||
/// The bottom right corner
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
|
@ -255,6 +269,7 @@ impl AnchorCorner {
|
|||
Bounds { origin, size }
|
||||
}
|
||||
|
||||
/// Get the point corresponding to this anchor corner in `bounds`.
|
||||
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||
match self {
|
||||
Self::TopLeft => bounds.origin,
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use crate::{
|
||||
Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
|
||||
IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
|
||||
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
|
||||
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
/// An SVG element.
|
||||
pub struct Svg {
|
||||
interactivity: Interactivity,
|
||||
path: Option<SharedString>,
|
||||
}
|
||||
|
||||
/// Create a new SVG element.
|
||||
pub fn svg() -> Svg {
|
||||
Svg {
|
||||
interactivity: Interactivity::default(),
|
||||
|
@ -17,6 +19,7 @@ pub fn svg() -> Svg {
|
|||
}
|
||||
|
||||
impl Svg {
|
||||
/// Set the path to the SVG file for this element.
|
||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
|
@ -29,7 +32,7 @@ impl Element for Svg {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
self.interactivity.layout(element_state, cx, |style, cx| {
|
||||
cx.request_layout(&style, None)
|
||||
|
@ -40,7 +43,7 @@ impl Element for Svg {
|
|||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use crate::{
|
||||
Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
|
||||
MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
|
||||
WhiteSpace, WindowContext, WrappedLine,
|
||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
||||
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||
TOOLTIP_DELAY,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
mem,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
impl Element for &'static str {
|
||||
|
@ -15,14 +22,14 @@ impl Element for &'static str {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
state.paint(bounds, self, cx)
|
||||
}
|
||||
}
|
||||
|
@ -45,14 +52,14 @@ impl Element for SharedString {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.clone(), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
let text_str: &str = self.as_ref();
|
||||
state.paint(bounds, text_str, cx)
|
||||
}
|
||||
|
@ -81,6 +88,7 @@ pub struct StyledText {
|
|||
}
|
||||
|
||||
impl StyledText {
|
||||
/// Construct a new styled text element from the given string.
|
||||
pub fn new(text: impl Into<SharedString>) -> Self {
|
||||
StyledText {
|
||||
text: text.into(),
|
||||
|
@ -88,6 +96,8 @@ impl StyledText {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the styling attributes for the given text, as well as
|
||||
/// as any ranges of text that have had their style customized.
|
||||
pub fn with_highlights(
|
||||
mut self,
|
||||
default_style: &TextStyle,
|
||||
|
@ -121,14 +131,14 @@ impl Element for StyledText {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
state.paint(bounds, &self.text, cx)
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +155,7 @@ impl IntoElement for StyledText {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
|
||||
|
||||
|
@ -164,7 +175,7 @@ impl TextState {
|
|||
&mut self,
|
||||
text: SharedString,
|
||||
runs: Option<Vec<TextRun>>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> LayoutId {
|
||||
let text_style = cx.text_style();
|
||||
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
||||
|
@ -239,7 +250,7 @@ impl TextState {
|
|||
layout_id
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut ElementContext) {
|
||||
let element_state = self.lock();
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
|
@ -284,11 +295,14 @@ impl TextState {
|
|||
}
|
||||
}
|
||||
|
||||
/// A text element that can be interacted with.
|
||||
pub struct InteractiveText {
|
||||
element_id: ElementId,
|
||||
text: StyledText,
|
||||
click_listener:
|
||||
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
|
||||
hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>)>>,
|
||||
tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext<'_>) -> Option<AnyView>>>,
|
||||
clickable_ranges: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
|
@ -297,21 +311,30 @@ struct InteractiveTextClickEvent {
|
|||
mouse_up_index: usize,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct InteractiveTextState {
|
||||
text_state: TextState,
|
||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
||||
hovered_index: Rc<Cell<Option<usize>>>,
|
||||
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
|
||||
}
|
||||
|
||||
/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
|
||||
impl InteractiveText {
|
||||
/// Creates a new InteractiveText from the given text.
|
||||
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
|
||||
Self {
|
||||
element_id: id.into(),
|
||||
text,
|
||||
click_listener: None,
|
||||
hover_listener: None,
|
||||
tooltip_builder: None,
|
||||
clickable_ranges: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// on_click is called when the user clicks on one of the given ranges, passing the index of
|
||||
/// the clicked range.
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
ranges: Vec<Range<usize>>,
|
||||
|
@ -328,6 +351,25 @@ impl InteractiveText {
|
|||
self.clickable_ranges = ranges;
|
||||
self
|
||||
}
|
||||
|
||||
/// on_hover is called when the mouse moves over a character within the text, passing the
|
||||
/// index of the hovered character, or None if the mouse leaves the text.
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
listener: impl Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>) + 'static,
|
||||
) -> Self {
|
||||
self.hover_listener = Some(Box::new(listener));
|
||||
self
|
||||
}
|
||||
|
||||
/// tooltip lets you specify a tooltip for a given character index in the string.
|
||||
pub fn tooltip(
|
||||
mut self,
|
||||
builder: impl Fn(usize, &mut WindowContext<'_>) -> Option<AnyView> + 'static,
|
||||
) -> Self {
|
||||
self.tooltip_builder = Some(Rc::new(builder));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for InteractiveText {
|
||||
|
@ -336,16 +378,21 @@ impl Element for InteractiveText {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
if let Some(InteractiveTextState {
|
||||
mouse_down_index, ..
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
..
|
||||
}) = state
|
||||
{
|
||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
||||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
};
|
||||
(layout_id, element_state)
|
||||
} else {
|
||||
|
@ -353,12 +400,14 @@ impl Element for InteractiveText {
|
|||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index: Rc::default(),
|
||||
hovered_index: Rc::default(),
|
||||
active_tooltip: Rc::default(),
|
||||
};
|
||||
(layout_id, element_state)
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
if let Some(click_listener) = self.click_listener.take() {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
|
||||
|
@ -408,6 +457,83 @@ impl Element for InteractiveText {
|
|||
});
|
||||
}
|
||||
}
|
||||
if let Some(hover_listener) = self.hover_listener.take() {
|
||||
let text_state = state.text_state.clone();
|
||||
let hovered_index = state.hovered_index.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
let current = hovered_index.get();
|
||||
let updated = text_state.index_for_position(bounds, event.position);
|
||||
if current != updated {
|
||||
hovered_index.set(updated);
|
||||
hover_listener(updated, event.clone(), cx);
|
||||
cx.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
let pending_mouse_down = state.mouse_down_index.clone();
|
||||
let text_state = state.text_state.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
let position = text_state.index_for_position(bounds, event.position);
|
||||
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.take();
|
||||
return;
|
||||
}
|
||||
let position = position.unwrap();
|
||||
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
if active_tooltip.borrow().is_none() {
|
||||
let task = cx.spawn({
|
||||
let active_tooltip = active_tooltip.clone();
|
||||
let tooltip_builder = tooltip_builder.clone();
|
||||
|
||||
move |mut cx| async move {
|
||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||
cx.update(|cx| {
|
||||
let new_tooltip =
|
||||
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
|
||||
tooltip: Some(AnyTooltip {
|
||||
view: tooltip,
|
||||
cursor_offset: cx.mouse_position(),
|
||||
}),
|
||||
_task: None,
|
||||
});
|
||||
*active_tooltip.borrow_mut() = new_tooltip;
|
||||
cx.refresh();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
|
||||
tooltip: None,
|
||||
_task: Some(task),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
||||
active_tooltip.take();
|
||||
});
|
||||
|
||||
if let Some(tooltip) = state
|
||||
.active_tooltip
|
||||
.clone()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|at| at.tooltip.clone())
|
||||
{
|
||||
cx.set_tooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
self.text.paint(bounds, &mut state.text_state, cx)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
//! A scrollable list of elements with uniform height, optimized for large lists.
|
||||
//! Rather than use the full taffy layout system, uniform_list simply measures
|
||||
//! the first element and then lays out all remaining elements in a line based on that
|
||||
//! measurement. This is much faster than the full layout system, but only works for
|
||||
//! elements with uniform height.
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
|
||||
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
||||
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||
};
|
||||
|
@ -53,6 +59,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
|
||||
pub struct UniformList {
|
||||
id: ElementId,
|
||||
item_count: usize,
|
||||
|
@ -63,18 +70,22 @@ pub struct UniformList {
|
|||
scroll_handle: Option<UniformListScrollHandle>,
|
||||
}
|
||||
|
||||
/// A handle for controlling the scroll position of a uniform list.
|
||||
/// This should be stored in your view and passed to the uniform_list on each frame.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UniformListScrollHandle {
|
||||
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
|
||||
}
|
||||
|
||||
impl UniformListScrollHandle {
|
||||
/// Create a new scroll handle to bind to a uniform list.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
deferred_scroll_to_item: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item index.
|
||||
pub fn scroll_to_item(&mut self, ix: usize) {
|
||||
self.deferred_scroll_to_item.replace(Some(ix));
|
||||
}
|
||||
|
@ -86,6 +97,7 @@ impl Styled for UniformList {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default)]
|
||||
pub struct UniformListState {
|
||||
interactive: InteractiveElementState,
|
||||
|
@ -98,7 +110,7 @@ impl Element for UniformList {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let max_items = self.item_count;
|
||||
let item_size = state
|
||||
|
@ -146,7 +158,7 @@ impl Element for UniformList {
|
|||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let style =
|
||||
self.interactivity
|
||||
|
@ -262,12 +274,13 @@ impl IntoElement for UniformList {
|
|||
}
|
||||
|
||||
impl UniformList {
|
||||
/// Selects a specific list item for measurement.
|
||||
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
||||
self.item_to_measure_index = item_index.unwrap_or(0);
|
||||
self
|
||||
}
|
||||
|
||||
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
|
||||
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut ElementContext) -> Size<Pixels> {
|
||||
if self.item_count == 0 {
|
||||
return Size::default();
|
||||
}
|
||||
|
@ -284,6 +297,7 @@ impl UniformList {
|
|||
item_to_measure.measure(available_space, cx)
|
||||
}
|
||||
|
||||
/// Track and render scroll state of this list with reference to the given scroll handle.
|
||||
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
||||
self.scroll_handle = Some(handle);
|
||||
self
|
||||
|
|
|
@ -21,11 +21,15 @@ use waker_fn::waker_fn;
|
|||
#[cfg(any(test, feature = "test-support"))]
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
/// A pointer to the executor that is currently running,
|
||||
/// for spawning background tasks.
|
||||
#[derive(Clone)]
|
||||
pub struct BackgroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
}
|
||||
|
||||
/// A pointer to the executor that is currently running,
|
||||
/// for spawning tasks on the main thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ForegroundExecutor {
|
||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||
|
@ -37,11 +41,14 @@ pub struct ForegroundExecutor {
|
|||
/// It implements [`Future`] so you can `.await` on it.
|
||||
///
|
||||
/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
|
||||
/// the task to continue running in the background, but with no way to return a value.
|
||||
/// the task to continue running, but with no way to return a value.
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub enum Task<T> {
|
||||
/// A task that is ready to return a value
|
||||
Ready(Option<T>),
|
||||
|
||||
/// A task that is currently running.
|
||||
Spawned(async_task::Task<T>),
|
||||
}
|
||||
|
||||
|
@ -87,6 +94,8 @@ impl<T> Future for Task<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A task label is an opaque identifier that you can use to
|
||||
/// refer to a task in tests.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TaskLabel(NonZeroUsize);
|
||||
|
||||
|
@ -97,6 +106,7 @@ impl Default for TaskLabel {
|
|||
}
|
||||
|
||||
impl TaskLabel {
|
||||
/// Construct a new task label.
|
||||
pub fn new() -> Self {
|
||||
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
|
||||
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
|
||||
|
@ -363,6 +373,7 @@ impl BackgroundExecutor {
|
|||
|
||||
/// ForegroundExecutor runs things on the main thread.
|
||||
impl ForegroundExecutor {
|
||||
/// Creates a new ForegroundExecutor from the given PlatformDispatcher.
|
||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||
Self {
|
||||
dispatcher,
|
||||
|
@ -411,13 +422,14 @@ impl<'a> Scope<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Spawn a future into this scope.
|
||||
pub fn spawn<F>(&mut self, f: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'a,
|
||||
{
|
||||
let tx = self.tx.clone().unwrap();
|
||||
|
||||
// Safety: The 'a lifetime is guaranteed to outlive any of these futures because
|
||||
// SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
|
||||
// dropping this `Scope` blocks until all of the futures have resolved.
|
||||
let f = unsafe {
|
||||
mem::transmute::<
|
||||
|
|
|
@ -1,3 +1,31 @@
|
|||
//! # Welcome to GPUI!
|
||||
//!
|
||||
//! GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
|
||||
//! for Rust, designed to support a wide variety of applications. GPUI is currently
|
||||
//! being actively developed and improved for the [Zed code editor](https://zed.dev/), and new versions
|
||||
//! will have breaking changes. You'll probably need to use the latest stable version
|
||||
//! of rust to use GPUI.
|
||||
//!
|
||||
//! # Getting started with GPUI
|
||||
//!
|
||||
//! TODO!(docs): Write a code sample showing how to create a window and render a simple
|
||||
//! div
|
||||
//!
|
||||
//! # Drawing interesting things
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show how to draw a more interesting scene, with
|
||||
//! a counter to store state and a button to increment it.
|
||||
//!
|
||||
//! # Interacting with your application state
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show GPUI entity interactions, like subscriptions and entities
|
||||
//! maybe make a network request to show async stuff?
|
||||
//!
|
||||
//! # Conclusion
|
||||
//!
|
||||
//! TODO!(docs): Wrap up with a conclusion and links to other places? Zed / GPUI website?
|
||||
//! Discord for chatting about it? Other tutorials or references?
|
||||
|
||||
#[macro_use]
|
||||
mod action;
|
||||
mod app;
|
||||
|
@ -58,10 +86,10 @@ pub use elements::*;
|
|||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui_macros::{register_action, test, IntoElement, Render};
|
||||
pub use image_cache::*;
|
||||
use image_cache::*;
|
||||
pub use input::*;
|
||||
pub use interactive::*;
|
||||
pub use key_dispatch::*;
|
||||
use key_dispatch::*;
|
||||
pub use keymap::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
|
@ -73,7 +101,7 @@ pub use smol::Timer;
|
|||
pub use style::*;
|
||||
pub use styled::*;
|
||||
pub use subscription::*;
|
||||
pub use svg_renderer::*;
|
||||
use svg_renderer::*;
|
||||
pub use taffy::{AvailableSpace, LayoutId};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test::*;
|
||||
|
@ -82,20 +110,23 @@ pub use util::arc_cow::ArcCow;
|
|||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::BorrowMut,
|
||||
};
|
||||
use std::{any::Any, borrow::BorrowMut};
|
||||
use taffy::TaffyLayoutEngine;
|
||||
|
||||
/// The context trait, allows the different contexts in GPUI to be used
|
||||
/// interchangeably for certain operations.
|
||||
pub trait Context {
|
||||
/// The result type for this context, used for async contexts that
|
||||
/// can't hold a direct reference to the application context.
|
||||
type Result<T>;
|
||||
|
||||
/// Create a new model in the app context.
|
||||
fn new_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>>;
|
||||
|
||||
/// Update a model in the app context.
|
||||
fn update_model<T, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
|
@ -104,6 +135,7 @@ pub trait Context {
|
|||
where
|
||||
T: 'static;
|
||||
|
||||
/// Read a model from the app context.
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
|
@ -112,10 +144,12 @@ pub trait Context {
|
|||
where
|
||||
T: 'static;
|
||||
|
||||
/// Update a window for the given handle.
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
||||
|
||||
/// Read a window off of the application context.
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
|
@ -125,7 +159,10 @@ pub trait Context {
|
|||
T: 'static;
|
||||
}
|
||||
|
||||
/// This trait is used for the different visual contexts in GPUI that
|
||||
/// require a window to be present.
|
||||
pub trait VisualContext: Context {
|
||||
/// Construct a new view in the window referenced by this context.
|
||||
fn new_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
|
@ -133,12 +170,14 @@ pub trait VisualContext: Context {
|
|||
where
|
||||
V: 'static + Render;
|
||||
|
||||
/// Update a view with the given callback
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R>;
|
||||
|
||||
/// Replace the root view of a window with a new view.
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
|
@ -146,38 +185,42 @@ pub trait VisualContext: Context {
|
|||
where
|
||||
V: 'static + Render;
|
||||
|
||||
/// Focus a view in the window, if it implements the [`FocusableView`] trait.
|
||||
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: FocusableView;
|
||||
|
||||
/// Dismiss a view in the window, if it implements the [`ManagedView`] trait.
|
||||
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||
where
|
||||
V: ManagedView;
|
||||
}
|
||||
|
||||
/// A trait that allows models and views to be interchangeable in certain operations
|
||||
pub trait Entity<T>: Sealed {
|
||||
/// The weak reference type for this entity.
|
||||
type Weak: 'static;
|
||||
|
||||
/// The ID for this entity
|
||||
fn entity_id(&self) -> EntityId;
|
||||
|
||||
/// Downgrade this entity to a weak reference.
|
||||
fn downgrade(&self) -> Self::Weak;
|
||||
|
||||
/// Upgrade this entity from a weak reference.
|
||||
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// A trait for tying together the types of a GPUI entity and the events it can
|
||||
/// emit.
|
||||
pub trait EventEmitter<E: Any>: 'static {}
|
||||
|
||||
pub enum GlobalKey {
|
||||
Numeric(usize),
|
||||
View(EntityId),
|
||||
Type(TypeId),
|
||||
}
|
||||
|
||||
/// A helper trait for auto-implementing certain methods on contexts that
|
||||
/// can be used interchangeably.
|
||||
pub trait BorrowAppContext {
|
||||
fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
|
||||
/// Set a global value on the context.
|
||||
fn set_global<T: 'static>(&mut self, global: T);
|
||||
}
|
||||
|
||||
|
@ -185,26 +228,14 @@ impl<C> BorrowAppContext for C
|
|||
where
|
||||
C: BorrowMut<AppContext>,
|
||||
{
|
||||
fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
if let Some(style) = style {
|
||||
self.borrow_mut().push_text_style(style);
|
||||
let result = f(self);
|
||||
self.borrow_mut().pop_text_style();
|
||||
result
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_global<G: 'static>(&mut self, global: G) {
|
||||
self.borrow_mut().set_global(global)
|
||||
}
|
||||
}
|
||||
|
||||
/// A flatten equivalent for anyhow `Result`s.
|
||||
pub trait Flatten<T> {
|
||||
/// Convert this type into a simple `Result<T>`.
|
||||
fn flatten(self) -> Result<T>;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ use thiserror::Error;
|
|||
use util::http::{self, HttpClient};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub struct RenderImageParams {
|
||||
pub(crate) struct RenderImageParams {
|
||||
pub(crate) image_id: ImageId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum Error {
|
||||
pub(crate) enum Error {
|
||||
#[error("http error: {0}")]
|
||||
Client(#[from] http::Error),
|
||||
#[error("IO error: {0}")]
|
||||
|
@ -42,7 +42,7 @@ impl From<ImageError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ImageCache {
|
||||
pub(crate) struct ImageCache {
|
||||
client: Arc<dyn HttpClient>,
|
||||
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
|
||||
}
|
||||
|
|
|
@ -1,24 +1,35 @@
|
|||
use crate::{
|
||||
AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
|
||||
};
|
||||
use crate::{Bounds, InputHandler, Pixels, View, ViewContext, WindowContext};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
|
||||
///
|
||||
/// Once your view `V` implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
|
||||
/// Once your view implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
|
||||
/// This input handler can then be assigned during paint by calling [`WindowContext::handle_input`].
|
||||
pub trait InputHandler: 'static + Sized {
|
||||
///
|
||||
/// See [`InputHandler`] for details on how to implement each method.
|
||||
pub trait ViewInputHandler: 'static + Sized {
|
||||
/// See [`InputHandler::text_for_range`] for details
|
||||
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
||||
-> Option<String>;
|
||||
|
||||
/// See [`InputHandler::selected_text_range`] for details
|
||||
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
|
||||
/// See [`InputHandler::marked_text_range`] for details
|
||||
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
|
||||
/// See [`InputHandler::unmark_text`] for details
|
||||
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||
|
||||
/// See [`InputHandler::replace_text_in_range`] for details
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
|
||||
/// See [`InputHandler::replace_and_mark_text_in_range`] for details
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
|
@ -26,6 +37,8 @@ pub trait InputHandler: 'static + Sized {
|
|||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
|
||||
/// See [`InputHandler::bounds_for_range`] for details
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
|
@ -39,7 +52,6 @@ pub trait InputHandler: 'static + Sized {
|
|||
pub struct ElementInputHandler<V> {
|
||||
view: View<V>,
|
||||
element_bounds: Bounds<Pixels>,
|
||||
cx: AsyncWindowContext,
|
||||
}
|
||||
|
||||
impl<V: 'static> ElementInputHandler<V> {
|
||||
|
@ -47,45 +59,42 @@ impl<V: 'static> ElementInputHandler<V> {
|
|||
/// containing view.
|
||||
///
|
||||
/// [element_paint]: crate::Element::paint
|
||||
pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self {
|
||||
pub fn new(element_bounds: Bounds<Pixels>, view: View<V>) -> Self {
|
||||
ElementInputHandler {
|
||||
view,
|
||||
element_bounds,
|
||||
cx: cx.to_async(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
|
||||
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
||||
impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
|
||||
fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.selected_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.update(cx, |view, cx| view.selected_text_range(cx))
|
||||
}
|
||||
|
||||
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.marked_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||
self.view.update(cx, |view, cx| view.marked_text_range(cx))
|
||||
}
|
||||
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<String> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.text_for_range(range_utf16, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.update(cx, |view, cx| view.text_for_range(range_utf16, cx))
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.replace_text_in_range(replacement_range, text, cx)
|
||||
})
|
||||
.ok();
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
replacement_range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.view.update(cx, |view, cx| {
|
||||
view.replace_text_in_range(replacement_range, text, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
|
@ -93,26 +102,24 @@ impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
|
|||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||
})
|
||||
.ok();
|
||||
self.view.update(cx, |view, cx| {
|
||||
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| view.unmark_text(cx))
|
||||
.ok();
|
||||
fn unmark_text(&mut self, cx: &mut WindowContext) {
|
||||
self.view.update(cx, |view, cx| view.unmark_text(cx));
|
||||
}
|
||||
|
||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
||||
self.view
|
||||
.update(&mut self.cx, |view, cx| {
|
||||
view.bounds_for_range(range_utf16, self.element_bounds, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
self.view.update(cx, |view, cx| {
|
||||
view.bounds_for_range(range_utf16, self.element_bounds, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,25 @@ use crate::{
|
|||
use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
|
||||
|
||||
/// An event from a platform input source.
|
||||
pub trait InputEvent: Sealed + 'static {
|
||||
/// Convert this event into the platform input enum.
|
||||
fn to_platform_input(self) -> PlatformInput;
|
||||
}
|
||||
|
||||
/// A key event from the platform.
|
||||
pub trait KeyEvent: InputEvent {}
|
||||
|
||||
/// A mouse event from the platform.
|
||||
pub trait MouseEvent: InputEvent {}
|
||||
|
||||
/// The key down event equivalent for the platform.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct KeyDownEvent {
|
||||
/// The keystroke that was generated.
|
||||
pub keystroke: Keystroke,
|
||||
|
||||
/// Whether the key is currently held down.
|
||||
pub is_held: bool,
|
||||
}
|
||||
|
||||
|
@ -24,8 +34,10 @@ impl InputEvent for KeyDownEvent {
|
|||
}
|
||||
impl KeyEvent for KeyDownEvent {}
|
||||
|
||||
/// The key up event equivalent for the platform.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyUpEvent {
|
||||
/// The keystroke that was released.
|
||||
pub keystroke: Keystroke,
|
||||
}
|
||||
|
||||
|
@ -37,8 +49,10 @@ impl InputEvent for KeyUpEvent {
|
|||
}
|
||||
impl KeyEvent for KeyUpEvent {}
|
||||
|
||||
/// The modifiers changed event equivalent for the platform.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ModifiersChangedEvent {
|
||||
/// The new state of the modifier keys
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
|
@ -62,17 +76,28 @@ impl Deref for ModifiersChangedEvent {
|
|||
/// Based on the winit enum of the same name.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum TouchPhase {
|
||||
/// The touch started.
|
||||
Started,
|
||||
/// The touch event is moving.
|
||||
#[default]
|
||||
Moved,
|
||||
/// The touch phase has ended
|
||||
Ended,
|
||||
}
|
||||
|
||||
/// A mouse down event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseDownEvent {
|
||||
/// Which mouse button was pressed.
|
||||
pub button: MouseButton,
|
||||
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was pressed.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The number of times the button has been clicked.
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
|
@ -84,11 +109,19 @@ impl InputEvent for MouseDownEvent {
|
|||
}
|
||||
impl MouseEvent for MouseDownEvent {}
|
||||
|
||||
/// A mouse up event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseUpEvent {
|
||||
/// Which mouse button was released.
|
||||
pub button: MouseButton,
|
||||
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was released.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The number of times the button has been clicked.
|
||||
pub click_count: usize,
|
||||
}
|
||||
|
||||
|
@ -100,21 +133,34 @@ impl InputEvent for MouseUpEvent {
|
|||
}
|
||||
impl MouseEvent for MouseUpEvent {}
|
||||
|
||||
/// A click event, generated when a mouse button is pressed and released.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ClickEvent {
|
||||
/// The mouse event when the button was pressed.
|
||||
pub down: MouseDownEvent,
|
||||
|
||||
/// The mouse event when the button was released.
|
||||
pub up: MouseUpEvent,
|
||||
}
|
||||
|
||||
/// An enum representing the mouse button that was pressed.
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
/// The left mouse button.
|
||||
Left,
|
||||
|
||||
/// The right mouse button.
|
||||
Right,
|
||||
|
||||
/// The middle mouse button.
|
||||
Middle,
|
||||
|
||||
/// A navigation button, such as back or forward.
|
||||
Navigate(NavigationDirection),
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
/// Get all the mouse buttons in a list.
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
MouseButton::Left,
|
||||
|
@ -132,9 +178,13 @@ impl Default for MouseButton {
|
|||
}
|
||||
}
|
||||
|
||||
/// A navigation direction, such as back or forward.
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum NavigationDirection {
|
||||
/// The back button.
|
||||
Back,
|
||||
|
||||
/// The forward button.
|
||||
Forward,
|
||||
}
|
||||
|
||||
|
@ -144,10 +194,16 @@ impl Default for NavigationDirection {
|
|||
}
|
||||
}
|
||||
|
||||
/// A mouse move event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseMoveEvent {
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The mouse button that was pressed, if any.
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
|
@ -160,16 +216,25 @@ impl InputEvent for MouseMoveEvent {
|
|||
impl MouseEvent for MouseMoveEvent {}
|
||||
|
||||
impl MouseMoveEvent {
|
||||
/// Returns true if the left mouse button is currently held down.
|
||||
pub fn dragging(&self) -> bool {
|
||||
self.pressed_button == Some(MouseButton::Left)
|
||||
}
|
||||
}
|
||||
|
||||
/// A mouse wheel event from the platform
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScrollWheelEvent {
|
||||
/// The position of the mouse on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
||||
/// The change in scroll wheel position for this event.
|
||||
pub delta: ScrollDelta,
|
||||
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// The phase of the touch event.
|
||||
pub touch_phase: TouchPhase,
|
||||
}
|
||||
|
||||
|
@ -189,9 +254,12 @@ impl Deref for ScrollWheelEvent {
|
|||
}
|
||||
}
|
||||
|
||||
/// The scroll delta for a scroll wheel event.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollDelta {
|
||||
/// An exact scroll delta in pixels.
|
||||
Pixels(Point<Pixels>),
|
||||
/// An inexact scroll delta in lines.
|
||||
Lines(Point<f32>),
|
||||
}
|
||||
|
||||
|
@ -202,6 +270,7 @@ impl Default for ScrollDelta {
|
|||
}
|
||||
|
||||
impl ScrollDelta {
|
||||
/// Returns true if this is a precise scroll delta in pixels.
|
||||
pub fn precise(&self) -> bool {
|
||||
match self {
|
||||
ScrollDelta::Pixels(_) => true,
|
||||
|
@ -209,6 +278,7 @@ impl ScrollDelta {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts this scroll event into exact pixels.
|
||||
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
|
||||
match self {
|
||||
ScrollDelta::Pixels(delta) => *delta,
|
||||
|
@ -216,6 +286,7 @@ impl ScrollDelta {
|
|||
}
|
||||
}
|
||||
|
||||
/// Combines two scroll deltas into one.
|
||||
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
|
||||
match (self, other) {
|
||||
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
|
||||
|
@ -231,10 +302,15 @@ impl ScrollDelta {
|
|||
}
|
||||
}
|
||||
|
||||
/// A mouse exit event from the platform, generated when the mouse leaves the window.
|
||||
/// The position generated should be just outside of the window's bounds.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseExitEvent {
|
||||
/// The position of the mouse relative to the window.
|
||||
pub position: Point<Pixels>,
|
||||
/// The mouse button that was pressed, if any.
|
||||
pub pressed_button: Option<MouseButton>,
|
||||
/// The modifiers that were held down when the mouse was moved.
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
|
@ -254,10 +330,12 @@ impl Deref for MouseExitEvent {
|
|||
}
|
||||
}
|
||||
|
||||
/// A collection of paths from the platform, such as from a file drop.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||
|
||||
impl ExternalPaths {
|
||||
/// Convert this collection of paths into a slice.
|
||||
pub fn paths(&self) -> &[PathBuf] {
|
||||
&self.0
|
||||
}
|
||||
|
@ -269,18 +347,27 @@ impl Render for ExternalPaths {
|
|||
}
|
||||
}
|
||||
|
||||
/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileDropEvent {
|
||||
/// The files have entered the window.
|
||||
Entered {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
/// The paths of the files that are being dragged.
|
||||
paths: ExternalPaths,
|
||||
},
|
||||
/// The files are being dragged over the window
|
||||
Pending {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
},
|
||||
/// The files have been dropped onto the window.
|
||||
Submit {
|
||||
/// The position of the mouse relative to the window.
|
||||
position: Point<Pixels>,
|
||||
},
|
||||
/// The user has stopped dragging the files over the window.
|
||||
Exited,
|
||||
}
|
||||
|
||||
|
@ -292,40 +379,31 @@ impl InputEvent for FileDropEvent {
|
|||
}
|
||||
impl MouseEvent for FileDropEvent {}
|
||||
|
||||
/// An enum corresponding to all kinds of platform input events.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PlatformInput {
|
||||
/// A key was pressed.
|
||||
KeyDown(KeyDownEvent),
|
||||
/// A key was released.
|
||||
KeyUp(KeyUpEvent),
|
||||
/// The keyboard modifiers were changed.
|
||||
ModifiersChanged(ModifiersChangedEvent),
|
||||
/// The mouse was pressed.
|
||||
MouseDown(MouseDownEvent),
|
||||
/// The mouse was released.
|
||||
MouseUp(MouseUpEvent),
|
||||
/// The mouse was moved.
|
||||
MouseMove(MouseMoveEvent),
|
||||
/// The mouse exited the window.
|
||||
MouseExited(MouseExitEvent),
|
||||
/// The scroll wheel was used.
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
/// Files were dragged and dropped onto the window.
|
||||
FileDrop(FileDropEvent),
|
||||
}
|
||||
|
||||
impl PlatformInput {
|
||||
pub fn position(&self) -> Option<Point<Pixels>> {
|
||||
match self {
|
||||
PlatformInput::KeyDown { .. } => None,
|
||||
PlatformInput::KeyUp { .. } => None,
|
||||
PlatformInput::ModifiersChanged { .. } => None,
|
||||
PlatformInput::MouseDown(event) => Some(event.position),
|
||||
PlatformInput::MouseUp(event) => Some(event.position),
|
||||
PlatformInput::MouseMove(event) => Some(event.position),
|
||||
PlatformInput::MouseExited(event) => Some(event.position),
|
||||
PlatformInput::ScrollWheel(event) => Some(event.position),
|
||||
PlatformInput::FileDrop(FileDropEvent::Exited) => None,
|
||||
PlatformInput::FileDrop(
|
||||
FileDropEvent::Entered { position, .. }
|
||||
| FileDropEvent::Pending { position, .. }
|
||||
| FileDropEvent::Submit { position, .. },
|
||||
) => Some(*position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_event(&self) -> Option<&dyn Any> {
|
||||
pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
|
||||
match self {
|
||||
PlatformInput::KeyDown { .. } => None,
|
||||
PlatformInput::KeyUp { .. } => None,
|
||||
|
@ -339,7 +417,7 @@ impl PlatformInput {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn keyboard_event(&self) -> Option<&dyn Any> {
|
||||
pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
|
||||
match self {
|
||||
PlatformInput::KeyDown(event) => Some(event),
|
||||
PlatformInput::KeyUp(event) => Some(event),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch,
|
||||
Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
||||
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
|
||||
KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
||||
};
|
||||
use collections::FxHashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
@ -13,7 +13,7 @@ use std::{
|
|||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct DispatchNodeId(usize);
|
||||
pub(crate) struct DispatchNodeId(usize);
|
||||
|
||||
pub(crate) struct DispatchTree {
|
||||
node_stack: Vec<DispatchNodeId>,
|
||||
|
@ -36,7 +36,7 @@ pub(crate) struct DispatchNode {
|
|||
parent: Option<DispatchNodeId>,
|
||||
}
|
||||
|
||||
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
|
||||
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DispatchActionListener {
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
|
|||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A keybinding and it's associated metadata, from the keymap.
|
||||
pub struct KeyBinding {
|
||||
pub(crate) action: Box<dyn Action>,
|
||||
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
|
@ -19,10 +20,12 @@ impl Clone for KeyBinding {
|
|||
}
|
||||
|
||||
impl KeyBinding {
|
||||
/// Construct a new keybinding from the given data.
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
|
||||
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
|
||||
}
|
||||
|
||||
/// Load a keybinding from the given raw data.
|
||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||
let context = if let Some(context) = context {
|
||||
Some(KeyBindingContextPredicate::parse(context)?)
|
||||
|
@ -42,6 +45,7 @@ impl KeyBinding {
|
|||
})
|
||||
}
|
||||
|
||||
/// Check if the given keystrokes match this binding.
|
||||
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
||||
// If the binding is completed, push it onto the matches list
|
||||
|
@ -55,10 +59,12 @@ impl KeyBinding {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the keystrokes associated with this binding
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
||||
/// Get the action associated with this binding
|
||||
pub fn action(&self) -> &dyn Action {
|
||||
self.action.as_ref()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ use anyhow::{anyhow, Result};
|
|||
use smallvec::SmallVec;
|
||||
use std::fmt;
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// at this point in the element tree. Contains a set of identifiers
|
||||
/// and/or key value pairs representing the current context for the
|
||||
/// keymap.
|
||||
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||
|
||||
|
@ -21,6 +25,11 @@ impl<'a> TryFrom<&'a str> for KeyContext {
|
|||
}
|
||||
|
||||
impl KeyContext {
|
||||
/// Parse a key context from a string.
|
||||
/// The key context format is very simple:
|
||||
/// - either a single identifier, such as `StatusBar`
|
||||
/// - or a key value pair, such as `mode = visible`
|
||||
/// - separated by whitespace, such as `StatusBar mode = visible`
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let mut context = Self::default();
|
||||
let source = skip_whitespace(source);
|
||||
|
@ -53,14 +62,17 @@ impl KeyContext {
|
|||
Self::parse_expr(source, context)
|
||||
}
|
||||
|
||||
/// Check if this context is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Clear this context.
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
|
||||
/// Extend this context with another context.
|
||||
pub fn extend(&mut self, other: &Self) {
|
||||
for entry in &other.0 {
|
||||
if !self.contains(&entry.key) {
|
||||
|
@ -69,6 +81,7 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add an identifier to this context, if it's not already in this context.
|
||||
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
|
||||
let key = identifier.into();
|
||||
|
||||
|
@ -77,6 +90,7 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set a key value pair in this context, if it's not already set.
|
||||
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
||||
let key = key.into();
|
||||
if !self.contains(&key) {
|
||||
|
@ -87,10 +101,12 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if this context contains a given identifier or key.
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.0.iter().any(|entry| entry.key.as_ref() == key)
|
||||
}
|
||||
|
||||
/// Get the associated value for a given identifier or key.
|
||||
pub fn get(&self, key: &str) -> Option<&SharedString> {
|
||||
self.0
|
||||
.iter()
|
||||
|
@ -117,20 +133,31 @@ impl fmt::Debug for KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// Representing a small language for describing which contexts correspond
|
||||
/// to which actions.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum KeyBindingContextPredicate {
|
||||
/// A predicate that will match a given identifier.
|
||||
Identifier(SharedString),
|
||||
/// A predicate that will match a given key-value pair.
|
||||
Equal(SharedString, SharedString),
|
||||
/// A predicate that will match a given key-value pair not being present.
|
||||
NotEqual(SharedString, SharedString),
|
||||
/// A predicate that will match a given predicate appearing below another predicate.
|
||||
/// in the element tree
|
||||
Child(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// Predicate that will invert another predicate.
|
||||
Not(Box<KeyBindingContextPredicate>),
|
||||
/// A predicate that will match if both of its children match.
|
||||
And(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// A predicate that will match if either of its children match.
|
||||
Or(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
|
@ -138,6 +165,34 @@ pub enum KeyBindingContextPredicate {
|
|||
}
|
||||
|
||||
impl KeyBindingContextPredicate {
|
||||
/// Parse a string in the same format as the keymap's context field.
|
||||
///
|
||||
/// A basic equivalence check against a set of identifiers can performed by
|
||||
/// simply writing a string:
|
||||
///
|
||||
/// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
|
||||
///
|
||||
/// You can also specify a key-value pair:
|
||||
///
|
||||
/// `mode == visible` -> A predicate that will match a context with the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
/// And a logical operations combining these two checks:
|
||||
///
|
||||
/// `StatusBar && mode == visible` -> A predicate that will match a context with the
|
||||
/// identifier `StatusBar` and the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
///
|
||||
/// There is also a special child `>` operator that will match a predicate that is
|
||||
/// below another predicate:
|
||||
///
|
||||
/// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
|
||||
/// and a child context that has the key `mode` with the
|
||||
/// value `visible`
|
||||
///
|
||||
/// This syntax supports `!=`, `||` and `&&` as logical operators.
|
||||
/// You can also preface an operation or check with a `!` to negate it.
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let source = skip_whitespace(source);
|
||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
||||
|
@ -148,6 +203,7 @@ impl KeyBindingContextPredicate {
|
|||
}
|
||||
}
|
||||
|
||||
/// Eval a predicate against a set of contexts, arranged from lowest to highest.
|
||||
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
|
||||
let Some(context) = contexts.last() else {
|
||||
return false;
|
||||
|
|
|
@ -6,9 +6,12 @@ use std::{
|
|||
collections::HashMap,
|
||||
};
|
||||
|
||||
/// An opaque identifier of which version of the keymap is currently active.
|
||||
/// The keymap's version is changed whenever bindings are added or removed.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub struct KeymapVersion(usize);
|
||||
|
||||
/// A collection of key bindings for the user's application.
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<KeyBinding>,
|
||||
|
@ -19,16 +22,19 @@ pub struct Keymap {
|
|||
}
|
||||
|
||||
impl Keymap {
|
||||
/// Create a new keymap with the given bindings.
|
||||
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
/// Get the current version of the keymap.
|
||||
pub fn version(&self) -> KeymapVersion {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Add more bindings to the keymap.
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
let no_action_id = (NoAction {}).type_id();
|
||||
|
||||
|
@ -51,6 +57,7 @@ impl Keymap {
|
|||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
/// Reset this keymap to its initial state.
|
||||
pub fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
|
@ -77,6 +84,7 @@ impl Keymap {
|
|||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
/// Check if the given binding is enabled, given a certain key context.
|
||||
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
|
||||
// If binding has a context predicate, it must match the current context,
|
||||
if let Some(predicate) = &binding.context_predicate {
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
|
|||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeystrokeMatcher {
|
||||
pub(crate) struct KeystrokeMatcher {
|
||||
pending_keystrokes: Vec<Keystroke>,
|
||||
keymap: Arc<Mutex<Keymap>>,
|
||||
keymap_version: KeymapVersion,
|
||||
|
@ -35,7 +35,7 @@ impl KeystrokeMatcher {
|
|||
/// - KeyMatch::Complete(matches) =>
|
||||
/// One or more bindings have received the necessary key presses.
|
||||
/// Bindings added later will take precedence over earlier bindings.
|
||||
pub fn match_keystroke(
|
||||
pub(crate) fn match_keystroke(
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
context_stack: &[KeyContext],
|
||||
|
@ -73,9 +73,7 @@ impl KeystrokeMatcher {
|
|||
if !found_actions.is_empty() {
|
||||
self.pending_keystrokes.clear();
|
||||
return KeyMatch::Some(found_actions);
|
||||
}
|
||||
|
||||
if let Some(pending_key) = pending_key {
|
||||
} else if let Some(pending_key) = pending_key {
|
||||
self.pending_keystrokes.push(pending_key);
|
||||
KeyMatch::Pending
|
||||
} else {
|
||||
|
@ -85,6 +83,10 @@ impl KeystrokeMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
/// The result of matching a keystroke against a given keybinding.
|
||||
/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
|
||||
/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
|
||||
/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
|
||||
#[derive(Debug)]
|
||||
pub enum KeyMatch {
|
||||
None,
|
||||
|
@ -93,10 +95,12 @@ pub enum KeyMatch {
|
|||
}
|
||||
|
||||
impl KeyMatch {
|
||||
/// Returns true if the match is complete.
|
||||
pub fn is_some(&self) -> bool {
|
||||
matches!(self, KeyMatch::Some(_))
|
||||
}
|
||||
|
||||
/// Get the matches if the match is complete.
|
||||
pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
|
||||
match self {
|
||||
KeyMatch::Some(matches) => Some(matches),
|
||||
|
|
|
@ -6,4 +6,4 @@ mod matcher;
|
|||
pub use binding::*;
|
||||
pub use context::*;
|
||||
pub use keymap::*;
|
||||
pub use matcher::*;
|
||||
pub(crate) use matcher::*;
|
||||
|
|
|
@ -6,10 +6,10 @@ mod mac;
|
|||
mod test;
|
||||
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
|
||||
FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
|
||||
Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
|
||||
Size, TaskLabel,
|
||||
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
||||
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
||||
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||
Scene, SharedString, Size, TaskLabel, WindowContext,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
|
@ -34,9 +34,9 @@ use uuid::Uuid;
|
|||
pub use app_menu::*;
|
||||
pub use keystroke::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use mac::*;
|
||||
pub(crate) use mac::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test::*;
|
||||
pub(crate) use test::*;
|
||||
use time::UtcOffset;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -69,11 +69,10 @@ pub(crate) trait Platform: 'static {
|
|||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
);
|
||||
fn start_display_link(&self, display_id: DisplayId);
|
||||
fn stop_display_link(&self, display_id: DisplayId);
|
||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
|
||||
|
||||
fn open_url(&self, url: &str);
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||
|
@ -149,8 +148,8 @@ pub(crate) trait PlatformWindow {
|
|||
fn mouse_position(&self) -> Point<Pixels>;
|
||||
fn modifiers(&self) -> Modifiers;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
||||
fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||
fn activate(&self);
|
||||
fn set_title(&mut self, title: &str);
|
||||
|
@ -325,30 +324,168 @@ impl From<TileId> for etagere::AllocId {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PlatformInputHandler: 'static {
|
||||
fn selected_text_range(&mut self) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&mut self) -> Option<Range<usize>>;
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
|
||||
pub(crate) struct PlatformInputHandler {
|
||||
cx: AsyncWindowContext,
|
||||
handler: Box<dyn InputHandler>,
|
||||
}
|
||||
|
||||
impl PlatformInputHandler {
|
||||
pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
|
||||
Self { cx, handler }
|
||||
}
|
||||
|
||||
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
||||
self.cx
|
||||
.update(|cx| self.handler.selected_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
||||
self.cx
|
||||
.update(|cx| self.handler.marked_text_range(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
||||
self.cx
|
||||
.update(|cx| self.handler.text_for_range(range_utf16, cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
||||
self.cx
|
||||
.update(|cx| {
|
||||
self.handler
|
||||
.replace_text_in_range(replacement_range, text, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
);
|
||||
fn unmark_text(&mut self);
|
||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
|
||||
) {
|
||||
self.cx
|
||||
.update(|cx| {
|
||||
self.handler.replace_and_mark_text_in_range(
|
||||
range_utf16,
|
||||
new_text,
|
||||
new_selected_range,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
|
||||
}
|
||||
|
||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
||||
self.cx
|
||||
.update(|cx| self.handler.bounds_for_range(range_utf16, cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// Zed's interface for handling text input from the platform's IME system
|
||||
/// This is currently a 1:1 exposure of the NSTextInputClient API:
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
|
||||
pub trait InputHandler: 'static {
|
||||
/// Get the range of the user's currently selected text, if any
|
||||
/// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
|
||||
///
|
||||
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
||||
fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
||||
|
||||
/// Get the range of the currently marked text, if any
|
||||
/// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
|
||||
///
|
||||
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
||||
fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
||||
|
||||
/// Get the text for the given document range in UTF-16 characters
|
||||
/// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
|
||||
///
|
||||
/// range_utf16 is in terms of UTF-16 characters
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<String>;
|
||||
|
||||
/// Replace the text in the given document range with the given text
|
||||
/// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
|
||||
///
|
||||
/// replacement_range is in terms of UTF-16 characters
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
replacement_range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
|
||||
/// Replace the text in the given document range with the given text,
|
||||
/// and mark the given text as part of of an IME 'composing' state
|
||||
/// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
|
||||
///
|
||||
/// range_utf16 is in terms of UTF-16 characters
|
||||
/// new_selected_range is in terms of UTF-16 characters
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
|
||||
/// Remove the IME 'composing' state from the document
|
||||
/// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
|
||||
fn unmark_text(&mut self, cx: &mut WindowContext);
|
||||
|
||||
/// Get the bounds of the given document range in screen coordinates
|
||||
/// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
|
||||
///
|
||||
/// This is used for positioning the IME candidate window
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Bounds<Pixels>>;
|
||||
}
|
||||
|
||||
/// The variables that can be configured when creating a new window
|
||||
#[derive(Debug)]
|
||||
pub struct WindowOptions {
|
||||
/// The initial bounds of the window
|
||||
pub bounds: WindowBounds,
|
||||
|
||||
/// The titlebar configuration of the window
|
||||
pub titlebar: Option<TitlebarOptions>,
|
||||
|
||||
/// Whether the window should be centered on the screen
|
||||
pub center: bool,
|
||||
|
||||
/// Whether the window should be focused when created
|
||||
pub focus: bool,
|
||||
|
||||
/// Whether the window should be shown when created
|
||||
pub show: bool,
|
||||
|
||||
/// The kind of window to create
|
||||
pub kind: WindowKind,
|
||||
|
||||
/// Whether the window should be movable by the user
|
||||
pub is_movable: bool,
|
||||
|
||||
/// The display to create the window on
|
||||
pub display_id: Option<DisplayId>,
|
||||
}
|
||||
|
||||
|
@ -371,46 +508,67 @@ impl Default for WindowOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// The options that can be configured for a window's titlebar
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TitlebarOptions {
|
||||
/// The initial title of the window
|
||||
pub title: Option<SharedString>,
|
||||
|
||||
/// Whether the titlebar should appear transparent
|
||||
pub appears_transparent: bool,
|
||||
|
||||
/// The position of the macOS traffic light buttons
|
||||
pub traffic_light_position: Option<Point<Pixels>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Appearance {
|
||||
Light,
|
||||
VibrantLight,
|
||||
Dark,
|
||||
VibrantDark,
|
||||
}
|
||||
|
||||
impl Default for Appearance {
|
||||
fn default() -> Self {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of window to create
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WindowKind {
|
||||
/// A normal application window
|
||||
Normal,
|
||||
|
||||
/// A window that appears above all other windows, usually used for alerts or popups
|
||||
/// use sparingly!
|
||||
PopUp,
|
||||
}
|
||||
|
||||
/// Which bounds algorithm to use for the initial size a window
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub enum WindowBounds {
|
||||
/// The window should be full screen, on macOS this corresponds to the full screen feature
|
||||
Fullscreen,
|
||||
|
||||
/// Make the window as large as the current display's size.
|
||||
#[default]
|
||||
Maximized,
|
||||
|
||||
/// Set the window to the given size in pixels
|
||||
Fixed(Bounds<GlobalPixels>),
|
||||
}
|
||||
|
||||
/// The appearance of the window, as defined by the operating system
|
||||
/// On macOS, this corresponds to named [NSAppearance](https://developer.apple.com/documentation/appkit/nsappearance)
|
||||
/// values
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WindowAppearance {
|
||||
/// A light appearance
|
||||
///
|
||||
/// on macOS, this corresponds to the `aqua` appearance
|
||||
Light,
|
||||
|
||||
/// A light appearance with vibrant colors
|
||||
///
|
||||
/// on macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance
|
||||
VibrantLight,
|
||||
|
||||
/// A dark appearance
|
||||
///
|
||||
/// on macOS, this corresponds to the `darkAqua` appearance
|
||||
Dark,
|
||||
|
||||
/// A dark appearance with vibrant colors
|
||||
///
|
||||
/// on macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance
|
||||
VibrantDark,
|
||||
}
|
||||
|
||||
|
@ -420,40 +578,102 @@ impl Default for WindowAppearance {
|
|||
}
|
||||
}
|
||||
|
||||
/// The options that can be configured for a file dialog prompt
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathPromptOptions {
|
||||
/// Should the prompt allow files to be selected?
|
||||
pub files: bool,
|
||||
/// Should the prompt allow directories to be selected?
|
||||
pub directories: bool,
|
||||
/// Should the prompt allow multiple files to be selected?
|
||||
pub multiple: bool,
|
||||
}
|
||||
|
||||
/// What kind of prompt styling to show
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PromptLevel {
|
||||
/// A prompt that is shown when the user should be notified of something
|
||||
Info,
|
||||
|
||||
/// A prompt that is shown when the user needs to be warned of a potential problem
|
||||
Warning,
|
||||
|
||||
/// A prompt that is shown when a critical problem has occurred
|
||||
Critical,
|
||||
}
|
||||
|
||||
/// The style of the cursor (pointer)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CursorStyle {
|
||||
/// The default cursor
|
||||
Arrow,
|
||||
|
||||
/// A text input cursor
|
||||
/// corresponds to the CSS cursor value `text`
|
||||
IBeam,
|
||||
|
||||
/// A crosshair cursor
|
||||
/// corresponds to the CSS cursor value `crosshair`
|
||||
Crosshair,
|
||||
|
||||
/// A closed hand cursor
|
||||
/// corresponds to the CSS cursor value `grabbing`
|
||||
ClosedHand,
|
||||
|
||||
/// An open hand cursor
|
||||
/// corresponds to the CSS cursor value `grab`
|
||||
OpenHand,
|
||||
|
||||
/// A pointing hand cursor
|
||||
/// corresponds to the CSS cursor value `pointer`
|
||||
PointingHand,
|
||||
|
||||
/// A resize left cursor
|
||||
/// corresponds to the CSS cursor value `w-resize`
|
||||
ResizeLeft,
|
||||
|
||||
/// A resize right cursor
|
||||
/// corresponds to the CSS cursor value `e-resize`
|
||||
ResizeRight,
|
||||
|
||||
/// A resize cursor to the left and right
|
||||
/// corresponds to the CSS cursor value `col-resize`
|
||||
ResizeLeftRight,
|
||||
|
||||
/// A resize up cursor
|
||||
/// corresponds to the CSS cursor value `n-resize`
|
||||
ResizeUp,
|
||||
|
||||
/// A resize down cursor
|
||||
/// corresponds to the CSS cursor value `s-resize`
|
||||
ResizeDown,
|
||||
|
||||
/// A resize cursor directing up and down
|
||||
/// corresponds to the CSS cursor value `row-resize`
|
||||
ResizeUpDown,
|
||||
|
||||
/// A cursor indicating that something will disappear if moved here
|
||||
/// Does not correspond to a CSS cursor value
|
||||
DisappearingItem,
|
||||
|
||||
/// A text input cursor for vertical layout
|
||||
/// corresponds to the CSS cursor value `vertical-text`
|
||||
IBeamCursorForVerticalLayout,
|
||||
|
||||
/// A cursor indicating that the operation is not allowed
|
||||
/// corresponds to the CSS cursor value `not-allowed`
|
||||
OperationNotAllowed,
|
||||
|
||||
/// A cursor indicating that the operation will result in a link
|
||||
/// corresponds to the CSS cursor value `alias`
|
||||
DragLink,
|
||||
|
||||
/// A cursor indicating that the operation will result in a copy
|
||||
/// corresponds to the CSS cursor value `copy`
|
||||
DragCopy,
|
||||
|
||||
/// A cursor indicating that the operation will result in a context menu
|
||||
/// corresponds to the CSS cursor value `context-menu`
|
||||
ContextualMenu,
|
||||
}
|
||||
|
||||
|
@ -463,6 +683,7 @@ impl Default for CursorStyle {
|
|||
}
|
||||
}
|
||||
|
||||
/// A datastructure representing a semantic version number
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct SemanticVersion {
|
||||
major: usize,
|
||||
|
@ -501,6 +722,7 @@ impl Display for SemanticVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// A clipboard item that should be copied to the clipboard
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ClipboardItem {
|
||||
pub(crate) text: String,
|
||||
|
@ -508,6 +730,7 @@ pub struct ClipboardItem {
|
|||
}
|
||||
|
||||
impl ClipboardItem {
|
||||
/// Create a new clipboard item with the given text
|
||||
pub fn new(text: String) -> Self {
|
||||
Self {
|
||||
text,
|
||||
|
@ -515,15 +738,18 @@ impl ClipboardItem {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new clipboard item with the given text and metadata
|
||||
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
|
||||
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the text of the clipboard item
|
||||
pub fn text(&self) -> &String {
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// Get the metadata of the clipboard item
|
||||
pub fn metadata<T>(&self) -> Option<T>
|
||||
where
|
||||
T: for<'a> Deserialize<'a>,
|
||||
|
|
|
@ -1,30 +1,49 @@
|
|||
use crate::{Action, AppContext, Platform};
|
||||
use util::ResultExt;
|
||||
|
||||
/// A menu of the application, either a main menu or a submenu
|
||||
pub struct Menu<'a> {
|
||||
/// The name of the menu
|
||||
pub name: &'a str,
|
||||
|
||||
/// The items in the menu
|
||||
pub items: Vec<MenuItem<'a>>,
|
||||
}
|
||||
|
||||
/// The different kinds of items that can be in a menu
|
||||
pub enum MenuItem<'a> {
|
||||
/// A separator between items
|
||||
Separator,
|
||||
|
||||
/// A submenu
|
||||
Submenu(Menu<'a>),
|
||||
|
||||
/// An action that can be performed
|
||||
Action {
|
||||
/// The name of this menu item
|
||||
name: &'a str,
|
||||
|
||||
/// the action to perform when this menu item is selected
|
||||
action: Box<dyn Action>,
|
||||
|
||||
/// The OS Action that corresponds to this action, if any
|
||||
/// See [`OsAction`] for more information
|
||||
os_action: Option<OsAction>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> MenuItem<'a> {
|
||||
/// Creates a new menu item that is a separator
|
||||
pub fn separator() -> Self {
|
||||
Self::Separator
|
||||
}
|
||||
|
||||
/// Creates a new menu item that is a submenu
|
||||
pub fn submenu(menu: Menu<'a>) -> Self {
|
||||
Self::Submenu(menu)
|
||||
}
|
||||
|
||||
/// Creates a new menu item that invokes an action
|
||||
pub fn action(name: &'a str, action: impl Action) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
|
@ -33,6 +52,7 @@ impl<'a> MenuItem<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new menu item that invokes an action and has an OS action
|
||||
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
|
||||
Self::Action {
|
||||
name,
|
||||
|
@ -42,13 +62,31 @@ impl<'a> MenuItem<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: As part of the global selections refactor, these should
|
||||
// be moved to GPUI-provided actions that make this association
|
||||
// without leaking the platform details to GPUI users
|
||||
|
||||
/// OS actions are actions that are recognized by the operating system
|
||||
/// This allows the operating system to provide specialized behavior for
|
||||
/// these actions
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum OsAction {
|
||||
/// The 'cut' action
|
||||
Cut,
|
||||
|
||||
/// The 'copy' action
|
||||
Copy,
|
||||
|
||||
/// The 'paste' action
|
||||
Paste,
|
||||
|
||||
/// The 'select all' action
|
||||
SelectAll,
|
||||
|
||||
/// The 'undo' action
|
||||
Undo,
|
||||
|
||||
/// The 'redo' action
|
||||
Redo,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,24 +3,31 @@ use serde::Deserialize;
|
|||
use smallvec::SmallVec;
|
||||
use std::fmt::Write;
|
||||
|
||||
/// A keystroke and associated metadata generated by the platform
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
/// the state of the modifier keys at the time the keystroke was generated
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// key is the character printed on the key that was pressed
|
||||
/// e.g. for option-s, key is "s"
|
||||
pub key: String,
|
||||
|
||||
/// ime_key is the character inserted by the IME engine when that key was pressed.
|
||||
/// e.g. for option-s, ime_key is "ß"
|
||||
pub ime_key: Option<String>,
|
||||
}
|
||||
|
||||
impl Keystroke {
|
||||
// When matching a key we cannot know whether the user intended to type
|
||||
// the ime_key or the key. On some non-US keyboards keys we use in our
|
||||
// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
|
||||
// and on some keyboards the IME handler converts a sequence of keys into a
|
||||
// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
|
||||
pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
|
||||
/// When matching a key we cannot know whether the user intended to type
|
||||
/// the ime_key or the key itself. On some non-US keyboards keys we use in our
|
||||
/// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
|
||||
/// and on some keyboards the IME handler converts a sequence of keys into a
|
||||
/// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
|
||||
///
|
||||
/// This method generates a list of potential keystroke candidates that could be matched
|
||||
/// against when resolving a keybinding.
|
||||
pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
|
||||
let mut possibilities = SmallVec::new();
|
||||
match self.ime_key.as_ref() {
|
||||
None => possibilities.push(self.clone()),
|
||||
|
@ -47,7 +54,7 @@ impl Keystroke {
|
|||
|
||||
/// key syntax is:
|
||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
|
||||
/// ime_key is only used for generating test events,
|
||||
/// ime_key syntax is only used for generating test events,
|
||||
/// when matching a key with an ime_key set will be matched without it.
|
||||
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
||||
let mut control = false;
|
||||
|
@ -135,16 +142,29 @@ impl std::fmt::Display for Keystroke {
|
|||
}
|
||||
}
|
||||
|
||||
/// The state of the modifier keys at some point in time
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Modifiers {
|
||||
/// The control key
|
||||
pub control: bool,
|
||||
|
||||
/// The alt key
|
||||
/// Sometimes also known as the 'meta' key
|
||||
pub alt: bool,
|
||||
|
||||
/// The shift key
|
||||
pub shift: bool,
|
||||
|
||||
/// The command key, on macos
|
||||
/// the windows key, on windows
|
||||
pub command: bool,
|
||||
|
||||
/// The function key
|
||||
pub function: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
/// Returns true if any modifier key is pressed
|
||||
pub fn modified(&self) -> bool {
|
||||
self.control || self.alt || self.shift || self.command || self.function
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ use metal_renderer::*;
|
|||
use objc::runtime::{BOOL, NO, YES};
|
||||
use std::ops::Range;
|
||||
|
||||
pub use dispatcher::*;
|
||||
pub use display::*;
|
||||
pub use display_linker::*;
|
||||
pub use metal_atlas::*;
|
||||
pub use platform::*;
|
||||
pub use text_system::*;
|
||||
pub use window::*;
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use display_linker::*;
|
||||
pub(crate) use metal_atlas::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
pub(crate) use window::*;
|
||||
|
||||
trait BoolExt {
|
||||
fn to_objc(self) -> BOOL;
|
||||
|
|
|
@ -24,7 +24,7 @@ pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
|
|||
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub struct MacDispatcher {
|
||||
pub(crate) struct MacDispatcher {
|
||||
parker: Arc<Mutex<Parker>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,18 +3,15 @@ use anyhow::Result;
|
|||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSDictionary, NSString},
|
||||
foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
};
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||
use core_graphics::{
|
||||
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
|
||||
geometry::{CGPoint, CGRect, CGSize},
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
|
||||
unsafe impl Send for MacDisplay {}
|
||||
|
||||
|
@ -24,11 +21,6 @@ impl MacDisplay {
|
|||
Self::all().find(|screen| screen.id() == id)
|
||||
}
|
||||
|
||||
/// Get the screen with the given persistent [`Uuid`].
|
||||
pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
|
||||
}
|
||||
|
||||
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||
/// corner is at the origin of the AppKit coordinate system.
|
||||
pub fn primary() -> Self {
|
||||
|
@ -77,14 +69,14 @@ extern "C" {
|
|||
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
|
||||
/// Convert the given rectangle from Cocoa's coordinate space to GPUI's coordinate space.
|
||||
///
|
||||
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards.
|
||||
pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
|
||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
||||
|
||||
Bounds {
|
||||
|
@ -101,22 +93,22 @@ pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
|
||||
/// Convert the given rectangle from GPUI's coordinate system to Cocoa's native coordinate space.
|
||||
///
|
||||
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards.
|
||||
pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
|
||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
||||
|
||||
CGRect::new(
|
||||
&CGPoint::new(
|
||||
NSRect::new(
|
||||
NSPoint::new(
|
||||
bounds.origin.x.into(),
|
||||
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
|
||||
),
|
||||
&CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
||||
NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -155,8 +147,20 @@ impl PlatformDisplay for MacDisplay {
|
|||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
unsafe {
|
||||
let native_bounds = CGDisplayBounds(self.0);
|
||||
display_bounds_from_native(native_bounds)
|
||||
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||
// the top left of the primary display.
|
||||
let bounds = CGDisplayBounds(self.0);
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(bounds.origin.x as f32),
|
||||
GlobalPixels(bounds.origin.y as f32),
|
||||
),
|
||||
size: size(
|
||||
GlobalPixels(bounds.size.width as f32),
|
||||
GlobalPixels(bounds.size.height as f32),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ use std::{
|
|||
use crate::DisplayId;
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
pub use sys::CVSMPTETime as SmtpeTime;
|
||||
pub use sys::CVTimeStamp as VideoTimestamp;
|
||||
|
||||
pub(crate) struct MacDisplayLinker {
|
||||
links: HashMap<DisplayId, MacDisplayLink>,
|
||||
|
@ -27,13 +25,13 @@ impl MacDisplayLinker {
|
|||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>>;
|
||||
type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
output_callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
|
@ -81,11 +79,11 @@ unsafe extern "C" fn trampoline(
|
|||
_flags_out: *mut i64,
|
||||
user_data: *mut c_void,
|
||||
) -> i32 {
|
||||
if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
let output_callback: Weak<OutputCallback> =
|
||||
Weak::from_raw(user_data as *mut OutputCallback);
|
||||
if let Some(output_callback) = output_callback.upgrade() {
|
||||
(output_callback.lock())(current_time, output_time)
|
||||
(output_callback.lock())()
|
||||
}
|
||||
mem::forget(output_callback);
|
||||
}
|
||||
|
@ -126,7 +124,7 @@ mod sys {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CVTimeStamp {
|
||||
pub(crate) struct CVTimeStamp {
|
||||
pub version: u32,
|
||||
pub video_time_scale: i32,
|
||||
pub video_time: i64,
|
||||
|
@ -154,7 +152,7 @@ mod sys {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct CVSMPTETime {
|
||||
pub(crate) struct CVSMPTETime {
|
||||
pub subframes: i16,
|
||||
pub subframe_divisor: i16,
|
||||
pub counter: u32,
|
||||
|
|
|
@ -83,7 +83,10 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
|
|||
}
|
||||
|
||||
impl PlatformInput {
|
||||
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
|
||||
pub(crate) unsafe fn from_native(
|
||||
native_event: id,
|
||||
window_height: Option<Pixels>,
|
||||
) -> Option<Self> {
|
||||
let event_type = native_event.eventType();
|
||||
|
||||
// Filter out event types that aren't in the NSEventType enum.
|
||||
|
|
|
@ -10,10 +10,10 @@ use metal::Device;
|
|||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
|
||||
impl MetalAtlas {
|
||||
pub fn new(device: Device) -> Self {
|
||||
pub(crate) fn new(device: Device) -> Self {
|
||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||
device: AssertSend(device),
|
||||
monochrome_textures: Default::default(),
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use block::ConcreteBlock;
|
||||
|
@ -139,9 +139,9 @@ unsafe fn build_classes() {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct MacPlatform(Mutex<MacPlatformState>);
|
||||
pub(crate) struct MacPlatform(Mutex<MacPlatformState>);
|
||||
|
||||
pub struct MacPlatformState {
|
||||
pub(crate) struct MacPlatformState {
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
|
@ -169,7 +169,7 @@ impl Default for MacPlatform {
|
|||
}
|
||||
|
||||
impl MacPlatform {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
let dispatcher = Arc::new(MacDispatcher::new());
|
||||
Self(Mutex::new(MacPlatformState {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
|
@ -475,10 +475,6 @@ impl Platform for MacPlatform {
|
|||
}
|
||||
}
|
||||
|
||||
// fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
|
||||
// Box::new(StatusItem::add(self.fonts()))
|
||||
// }
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
MacDisplay::all()
|
||||
.map(|screen| Rc::new(screen) as Rc<_>)
|
||||
|
@ -504,7 +500,7 @@ impl Platform for MacPlatform {
|
|||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
|
|
|
@ -41,7 +41,7 @@ use super::open_type;
|
|||
#[allow(non_upper_case_globals)]
|
||||
const kCGImageAlphaOnly: u32 = 7;
|
||||
|
||||
pub struct MacTextSystem(RwLock<MacTextSystemState>);
|
||||
pub(crate) struct MacTextSystem(RwLock<MacTextSystemState>);
|
||||
|
||||
struct MacTextSystemState {
|
||||
memory_source: MemSource,
|
||||
|
@ -54,7 +54,7 @@ struct MacTextSystemState {
|
|||
}
|
||||
|
||||
impl MacTextSystem {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(RwLock::new(MacTextSystemState {
|
||||
memory_source: MemSource::empty(),
|
||||
system_source: SystemSource::new(),
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use crate::{
|
||||
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
|
||||
FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
|
||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||
Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
|
@ -220,7 +220,7 @@ unsafe fn build_classes() {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
|
||||
pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
|
||||
point(
|
||||
px(position.x as f32),
|
||||
// MacOS screen coordinates are relative to bottom left
|
||||
|
@ -327,7 +327,7 @@ struct MacWindowState {
|
|||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||
close_callback: Option<Box<dyn FnOnce()>>,
|
||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
||||
last_key_equivalent: Option<KeyDownEvent>,
|
||||
synthetic_drag_counter: usize,
|
||||
|
@ -411,10 +411,8 @@ impl MacWindowState {
|
|||
}
|
||||
|
||||
fn frame(&self) -> Bounds<GlobalPixels> {
|
||||
unsafe {
|
||||
let frame = NSWindow::frame(self.native_window);
|
||||
display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
|
||||
}
|
||||
let frame = unsafe { NSWindow::frame(self.native_window) };
|
||||
global_bounds_from_ns_rect(frame)
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
|
@ -448,7 +446,7 @@ impl MacWindowState {
|
|||
|
||||
unsafe impl Send for MacWindowState {}
|
||||
|
||||
pub struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||
pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||
|
||||
impl MacWindow {
|
||||
pub fn open(
|
||||
|
@ -650,11 +648,11 @@ impl MacWindow {
|
|||
WindowBounds::Fixed(bounds) => {
|
||||
let display_bounds = display.bounds();
|
||||
let frame = if bounds.intersects(&display_bounds) {
|
||||
display_bounds_to_native(bounds)
|
||||
global_bounds_to_ns_rect(bounds)
|
||||
} else {
|
||||
display_bounds_to_native(display_bounds)
|
||||
global_bounds_to_ns_rect(display_bounds)
|
||||
};
|
||||
native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
|
||||
native_window.setFrame_display_(frame, YES);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,11 +764,11 @@ impl PlatformWindow for MacWindow {
|
|||
self
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.as_ref().lock().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>> {
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.as_ref().lock().input_handler.take()
|
||||
}
|
||||
|
||||
|
@ -1763,13 +1761,13 @@ fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id)
|
|||
|
||||
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut dyn PlatformInputHandler) -> R,
|
||||
F: FnOnce(&mut PlatformInputHandler) -> R,
|
||||
{
|
||||
let window_state = unsafe { get_window_state(window) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
if let Some(mut input_handler) = lock.input_handler.take() {
|
||||
drop(lock);
|
||||
let result = f(input_handler.as_mut());
|
||||
let result = f(&mut input_handler);
|
||||
window_state.lock().input_handler = Some(input_handler);
|
||||
Some(result)
|
||||
} else {
|
||||
|
|
|
@ -8,7 +8,7 @@ use objc::{msg_send, sel, sel_impl};
|
|||
use std::ffi::CStr;
|
||||
|
||||
impl WindowAppearance {
|
||||
pub unsafe fn from_native(appearance: id) -> Self {
|
||||
pub(crate) unsafe fn from_native(appearance: id) -> Self {
|
||||
let name: id = msg_send![appearance, name];
|
||||
if name == NSAppearanceNameVibrantLight {
|
||||
Self::VibrantLight
|
||||
|
|
|
@ -3,7 +3,7 @@ mod display;
|
|||
mod platform;
|
||||
mod window;
|
||||
|
||||
pub use dispatcher::*;
|
||||
pub use display::*;
|
||||
pub use platform::*;
|
||||
pub use window::*;
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use window::*;
|
||||
|
|
|
@ -18,6 +18,7 @@ use util::post_inc;
|
|||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct TestDispatcherId(usize);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct TestDispatcher {
|
||||
id: TestDispatcherId,
|
||||
state: Arc<Mutex<TestDispatcherState>>,
|
||||
|
|
|
@ -3,7 +3,7 @@ use anyhow::{Ok, Result};
|
|||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestDisplay {
|
||||
pub(crate) struct TestDisplay {
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
};
|
||||
|
||||
/// TestPlatform implements the Platform trait for use in tests.
|
||||
pub struct TestPlatform {
|
||||
pub(crate) struct TestPlatform {
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
|
||||
|
@ -178,20 +178,9 @@ impl Platform for TestPlatform {
|
|||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
_display_id: DisplayId,
|
||||
mut callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
|
||||
mut callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
let timestamp = crate::VideoTimestamp {
|
||||
version: 0,
|
||||
video_time_scale: 0,
|
||||
video_time: 0,
|
||||
host_time: 0,
|
||||
rate_scalar: 0.0,
|
||||
video_refresh_period: 0,
|
||||
smpte_time: crate::SmtpeTime::default(),
|
||||
flags: 0,
|
||||
reserved: 0,
|
||||
};
|
||||
callback(×tamp, ×tamp)
|
||||
callback()
|
||||
}
|
||||
|
||||
fn start_display_link(&self, _display_id: DisplayId) {}
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
|||
sync::{self, Arc},
|
||||
};
|
||||
|
||||
pub struct TestWindowState {
|
||||
pub(crate) struct TestWindowState {
|
||||
pub(crate) bounds: WindowBounds,
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
|
@ -23,11 +23,11 @@ pub struct TestWindowState {
|
|||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
moved_callback: Option<Box<dyn FnMut()>>,
|
||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
|
||||
pub(crate) struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
|
||||
|
||||
impl TestWindow {
|
||||
pub fn new(
|
||||
|
@ -117,9 +117,6 @@ impl TestWindow {
|
|||
|
||||
self.0.lock().input_handler = Some(input_handler);
|
||||
}
|
||||
pub fn edited(&self) -> bool {
|
||||
self.0.lock().edited
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindow for TestWindow {
|
||||
|
@ -163,11 +160,11 @@ impl PlatformWindow for TestWindow {
|
|||
self
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.lock().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>> {
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.lock().input_handler.take()
|
||||
}
|
||||
|
||||
|
@ -269,12 +266,12 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TestAtlasState {
|
||||
pub(crate) struct TestAtlasState {
|
||||
next_id: u32,
|
||||
tiles: HashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
pub struct TestAtlas(Mutex<TestAtlasState>);
|
||||
pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
|
||||
|
||||
impl TestAtlas {
|
||||
pub fn new() -> Self {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! The GPUI prelude is a collection of traits and types that are widely used
|
||||
//! throughout the library. It is recommended to import this prelude into your
|
||||
//! application to avoid having to import each trait individually.
|
||||
|
||||
pub use crate::{
|
||||
util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
|
||||
InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
|
||||
|
|
|
@ -10,12 +10,12 @@ pub(crate) type PointF = Point<f32>;
|
|||
#[allow(non_camel_case_types, unused)]
|
||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||
|
||||
pub type LayerId = u32;
|
||||
pub type DrawOrder = u32;
|
||||
pub(crate) type LayerId = u32;
|
||||
pub(crate) type DrawOrder = u32;
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(C)]
|
||||
pub struct ViewId {
|
||||
pub(crate) struct ViewId {
|
||||
low_bits: u32,
|
||||
high_bits: u32,
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ impl From<ViewId> for EntityId {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Scene {
|
||||
pub(crate) struct Scene {
|
||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
||||
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
||||
pub(crate) shadows: Vec<Shadow>,
|
||||
|
@ -153,49 +153,49 @@ impl Scene {
|
|||
for shadow in prev_scene.shadows.drain(..) {
|
||||
if views.contains(&shadow.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
|
||||
self.insert(&order, shadow);
|
||||
self.insert(order, shadow);
|
||||
}
|
||||
}
|
||||
|
||||
for quad in prev_scene.quads.drain(..) {
|
||||
if views.contains(&quad.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&quad.layer_id];
|
||||
self.insert(&order, quad);
|
||||
self.insert(order, quad);
|
||||
}
|
||||
}
|
||||
|
||||
for path in prev_scene.paths.drain(..) {
|
||||
if views.contains(&path.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&path.layer_id];
|
||||
self.insert(&order, path);
|
||||
self.insert(order, path);
|
||||
}
|
||||
}
|
||||
|
||||
for underline in prev_scene.underlines.drain(..) {
|
||||
if views.contains(&underline.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&underline.layer_id];
|
||||
self.insert(&order, underline);
|
||||
self.insert(order, underline);
|
||||
}
|
||||
}
|
||||
|
||||
for sprite in prev_scene.monochrome_sprites.drain(..) {
|
||||
if views.contains(&sprite.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
||||
self.insert(&order, sprite);
|
||||
self.insert(order, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
for sprite in prev_scene.polychrome_sprites.drain(..) {
|
||||
if views.contains(&sprite.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
||||
self.insert(&order, sprite);
|
||||
self.insert(order, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
for surface in prev_scene.surfaces.drain(..) {
|
||||
if views.contains(&surface.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&surface.layer_id];
|
||||
self.insert(&order, surface);
|
||||
self.insert(order, surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ impl<'a> Iterator for BatchIterator<'a> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub enum PrimitiveKind {
|
||||
pub(crate) enum PrimitiveKind {
|
||||
Shadow,
|
||||
#[default]
|
||||
Quad,
|
||||
|
@ -495,7 +495,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
|||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
pub(crate) struct Quad {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
|
@ -527,7 +527,7 @@ impl From<Quad> for Primitive {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Underline {
|
||||
pub(crate) struct Underline {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
|
@ -558,7 +558,7 @@ impl From<Underline> for Primitive {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Shadow {
|
||||
pub(crate) struct Shadow {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
|
@ -655,7 +655,7 @@ impl From<PolychromeSprite> for Primitive {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Surface {
|
||||
pub(crate) struct Surface {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
|
@ -685,6 +685,7 @@ impl From<Surface> for Primitive {
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
/// A line made up of a series of vertices and control points.
|
||||
#[derive(Debug)]
|
||||
pub struct Path<P: Clone + Default + Debug> {
|
||||
pub(crate) id: PathId,
|
||||
|
@ -701,6 +702,7 @@ pub struct Path<P: Clone + Default + Debug> {
|
|||
}
|
||||
|
||||
impl Path<Pixels> {
|
||||
/// Create a new path with the given starting point.
|
||||
pub fn new(start: Point<Pixels>) -> Self {
|
||||
Self {
|
||||
id: PathId(0),
|
||||
|
@ -720,6 +722,7 @@ impl Path<Pixels> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Scale this path by the given factor.
|
||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||
Path {
|
||||
id: self.id,
|
||||
|
@ -740,6 +743,7 @@ impl Path<Pixels> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw a straight line from the current point to the given point.
|
||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
|
@ -751,6 +755,7 @@ impl Path<Pixels> {
|
|||
self.current = to;
|
||||
}
|
||||
|
||||
/// Draw a curve from the current point to the given point, using the given control point.
|
||||
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
if self.contour_count > 1 {
|
||||
|
@ -833,7 +838,7 @@ impl From<Path<ScaledPixels>> for Primitive {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct PathVertex<P: Clone + Default + Debug> {
|
||||
pub(crate) struct PathVertex<P: Clone + Default + Debug> {
|
||||
pub(crate) xy_position: Point<P>,
|
||||
pub(crate) st_position: Point<f32>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
|
@ -850,4 +855,4 @@ impl PathVertex<Pixels> {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtlasId(pub(crate) usize);
|
||||
pub(crate) struct AtlasId(pub(crate) usize);
|
||||
|
|
|
@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
|
|||
use std::{borrow::Borrow, sync::Arc};
|
||||
use util::arc_cow::ArcCow;
|
||||
|
||||
/// A shared string is an immutable string that can be cheaply cloned in GPUI
|
||||
/// tasks. Essentially an abstraction over an Arc<str> and &'static str,
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::{iter, mem, ops::Range};
|
||||
|
||||
use crate::{
|
||||
black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds,
|
||||
ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement,
|
||||
Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
||||
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
|
||||
black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, Bounds, ContentMask, Corners,
|
||||
CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font,
|
||||
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
||||
SharedString, Size, SizeRefinement, Styled, TextRun,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use refineable::{Cascade, Refineable};
|
||||
|
@ -308,61 +308,12 @@ impl Style {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
|
||||
where
|
||||
C: BorrowAppContext,
|
||||
F: FnOnce(&mut C) -> R,
|
||||
{
|
||||
if self.text.is_some() {
|
||||
cx.with_text_style(Some(self.text.clone()), f)
|
||||
} else {
|
||||
f(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply overflow to content mask
|
||||
pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
|
||||
where
|
||||
C: BorrowWindow,
|
||||
F: FnOnce(&mut C) -> R,
|
||||
{
|
||||
let current_mask = cx.content_mask();
|
||||
|
||||
let min = current_mask.bounds.origin;
|
||||
let max = current_mask.bounds.lower_right();
|
||||
|
||||
let mask_bounds = match (
|
||||
self.overflow.x == Overflow::Visible,
|
||||
self.overflow.y == Overflow::Visible,
|
||||
) {
|
||||
// x and y both visible
|
||||
(true, true) => return f(cx),
|
||||
// x visible, y hidden
|
||||
(true, false) => Bounds::from_corners(
|
||||
point(min.x, bounds.origin.y),
|
||||
point(max.x, bounds.lower_right().y),
|
||||
),
|
||||
// x hidden, y visible
|
||||
(false, true) => Bounds::from_corners(
|
||||
point(bounds.origin.x, min.y),
|
||||
point(bounds.lower_right().x, max.y),
|
||||
),
|
||||
// both hidden
|
||||
(false, false) => bounds,
|
||||
};
|
||||
let mask = ContentMask {
|
||||
bounds: mask_bounds,
|
||||
};
|
||||
|
||||
cx.with_content_mask(Some(mask), f)
|
||||
}
|
||||
|
||||
/// Paints the background of an element styled with this style.
|
||||
pub fn paint(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
continuation: impl FnOnce(&mut WindowContext),
|
||||
cx: &mut ElementContext,
|
||||
continuation: impl FnOnce(&mut ElementContext),
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.debug_below {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::{
|
||||
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
|
||||
DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length,
|
||||
Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
|
||||
DefiniteLength, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, Position,
|
||||
SharedString, StyleRefinement, Visibility, WhiteSpace,
|
||||
};
|
||||
use crate::{BoxShadow, TextStyleRefinement};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use taffy::style::Overflow;
|
||||
use taffy::style::{Display, Overflow};
|
||||
|
||||
pub trait Styled: Sized {
|
||||
fn style(&mut self) -> &mut StyleRefinement;
|
||||
|
|
|
@ -147,12 +147,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A handle to a subscription created by GPUI. When dropped, the subscription
|
||||
/// is cancelled and the callback will no longer be invoked.
|
||||
#[must_use]
|
||||
pub struct Subscription {
|
||||
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
/// Detaches the subscription from this handle. The callback will
|
||||
/// continue to be invoked until the views or models it has been
|
||||
/// subscribed to are dropped
|
||||
pub fn detach(mut self) {
|
||||
self.unsubscribe.take();
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ use anyhow::anyhow;
|
|||
use std::{hash::Hash, sync::Arc};
|
||||
|
||||
#[derive(Clone, PartialEq, Hash, Eq)]
|
||||
pub struct RenderSvgParams {
|
||||
pub(crate) struct RenderSvgParams {
|
||||
pub(crate) path: SharedString,
|
||||
pub(crate) size: Size<DevicePixels>,
|
||||
}
|
||||
|
||||
pub struct SvgRenderer {
|
||||
pub(crate) struct SvgRenderer {
|
||||
asset_source: Arc<dyn AssetSource>,
|
||||
}
|
||||
|
||||
|
|
|
@ -229,6 +229,7 @@ impl TaffyLayoutEngine {
|
|||
}
|
||||
}
|
||||
|
||||
/// A unique identifier for a layout node, generated when requesting a layout from Taffy
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct LayoutId(NodeId);
|
||||
|
@ -440,6 +441,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The space available for an element to be laid out in
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
|
||||
pub enum AvailableSpace {
|
||||
/// The amount of space available is the specified number of pixels
|
||||
|
|
|
@ -34,6 +34,9 @@ use std::{
|
|||
panic::{self, RefUnwindSafe},
|
||||
};
|
||||
|
||||
/// Run the given test function with the configured parameters.
|
||||
/// This is intended for use with the `gpui::test` macro
|
||||
/// and generally should not be used directly.
|
||||
pub fn run_test(
|
||||
mut num_iterations: u64,
|
||||
max_retries: usize,
|
||||
|
@ -78,6 +81,7 @@ pub fn run_test(
|
|||
}
|
||||
}
|
||||
|
||||
/// A test struct for converting an observation callback into a stream.
|
||||
pub struct Observation<T> {
|
||||
rx: channel::Receiver<T>,
|
||||
_subscription: Subscription,
|
||||
|
|
|
@ -377,7 +377,7 @@ impl TextSystem {
|
|||
Ok(lines)
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
||||
pub(crate) fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
||||
self.line_layout_cache.finish_frame(reused_views)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
black, fill, point, px, size, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result,
|
||||
SharedString, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
||||
black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result,
|
||||
SharedString, UnderlineStyle, WrapBoundary, WrappedLineLayout,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -24,6 +24,7 @@ pub struct ShapedLine {
|
|||
}
|
||||
|
||||
impl ShapedLine {
|
||||
/// The length of the line in utf-8 bytes.
|
||||
pub fn len(&self) -> usize {
|
||||
self.layout.len
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ impl ShapedLine {
|
|||
&self,
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Result<()> {
|
||||
paint_line(
|
||||
origin,
|
||||
|
@ -65,7 +66,7 @@ impl WrappedLine {
|
|||
&self,
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> Result<()> {
|
||||
paint_line(
|
||||
origin,
|
||||
|
@ -86,7 +87,7 @@ fn paint_line(
|
|||
line_height: Pixels,
|
||||
decoration_runs: &[DecorationRun],
|
||||
wrap_boundaries: &[WrapBoundary],
|
||||
cx: &mut WindowContext<'_>,
|
||||
cx: &mut ElementContext<'_>,
|
||||
) -> Result<()> {
|
||||
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
use crate::{
|
||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
|
||||
Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
|
||||
IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle,
|
||||
ViewContext, VisualContext, WeakModel, WindowContext,
|
||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
|
||||
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
|
||||
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
|
||||
TextStyle, ViewContext, VisualContext, WeakModel,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
|
@ -96,7 +94,7 @@ impl<V: Render> Element for View<V> {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
cx.with_view_id(self.entity_id(), |cx| {
|
||||
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||
|
@ -105,7 +103,7 @@ impl<V: Render> Element for View<V> {
|
|||
})
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +202,7 @@ impl<V> Eq for WeakView<V> {}
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct AnyView {
|
||||
model: AnyModel,
|
||||
request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
|
||||
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
cache: bool,
|
||||
}
|
||||
|
||||
|
@ -252,7 +250,7 @@ impl AnyView {
|
|||
&self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
cx.with_absolute_element_offset(origin, |cx| {
|
||||
|
@ -280,7 +278,7 @@ impl Element for AnyView {
|
|||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
cx.with_view_id(self.entity_id(), |cx| {
|
||||
if self.cache {
|
||||
|
@ -301,7 +299,7 @@ impl Element for AnyView {
|
|||
})
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
if !self.cache {
|
||||
state.element.take().unwrap().paint(cx);
|
||||
|
@ -365,7 +363,7 @@ impl IntoElement for AnyView {
|
|||
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||
pub struct AnyWeakView {
|
||||
model: AnyWeakModel,
|
||||
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
|
||||
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
}
|
||||
|
||||
impl AnyWeakView {
|
||||
|
@ -404,11 +402,11 @@ impl std::fmt::Debug for AnyWeakView {
|
|||
}
|
||||
|
||||
mod any_view {
|
||||
use crate::{AnyElement, AnyView, IntoElement, LayoutId, Render, WindowContext};
|
||||
use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render};
|
||||
|
||||
pub(crate) fn request_layout<V: 'static + Render>(
|
||||
view: &AnyView,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, AnyElement) {
|
||||
let view = view.clone().downcast::<V>().unwrap();
|
||||
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||
|
|
File diff suppressed because it is too large
Load diff
1130
crates/gpui/src/window/element_cx.rs
Normal file
1130
crates/gpui/src/window/element_cx.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -102,7 +102,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut WindowContext) {
|
|||
cx.spawn(|mut cx| async move {
|
||||
let (journal_dir, entry_path) = create_entry.await?;
|
||||
let (workspace, _) = cx
|
||||
.update(|_, cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
|
||||
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
|
||||
.await?;
|
||||
|
||||
let opened = workspace
|
||||
|
|
|
@ -3007,7 +3007,7 @@ impl BufferSnapshot {
|
|||
groups.sort_by(|(id_a, group_a), (id_b, group_b)| {
|
||||
let a_start = &group_a.entries[group_a.primary_ix].range.start;
|
||||
let b_start = &group_b.entries[group_b.primary_ix].range.start;
|
||||
a_start.cmp(b_start, self).then_with(|| id_a.cmp(&id_b))
|
||||
a_start.cmp(b_start, self).then_with(|| id_a.cmp(id_b))
|
||||
});
|
||||
|
||||
groups
|
||||
|
|
|
@ -194,7 +194,7 @@ impl DiagnosticSet {
|
|||
.range
|
||||
.start
|
||||
.cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
|
||||
.then_with(|| id_a.cmp(&id_b))
|
||||
.then_with(|| id_a.cmp(id_b))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -379,8 +379,11 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CodeLabel {
|
||||
/// The text to display.
|
||||
pub text: String,
|
||||
/// Syntax highlighting runs.
|
||||
pub runs: Vec<(Range<usize>, HighlightId)>,
|
||||
/// The portion of the text that should be used in fuzzy filtering.
|
||||
pub filter_range: Range<usize>,
|
||||
}
|
||||
|
||||
|
@ -849,7 +852,7 @@ impl LanguageRegistry {
|
|||
let mut state = self.state.write();
|
||||
state.theme = Some(theme.clone());
|
||||
for language in &state.languages {
|
||||
language.set_theme(&theme.syntax());
|
||||
language.set_theme(theme.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1163,7 +1166,7 @@ impl LanguageRegistryState {
|
|||
|
||||
fn add(&mut self, language: Arc<Language>) {
|
||||
if let Some(theme) = self.theme.as_ref() {
|
||||
language.set_theme(&theme.syntax());
|
||||
language.set_theme(theme.syntax());
|
||||
}
|
||||
self.languages.push(language);
|
||||
self.version += 1;
|
||||
|
|
|
@ -91,6 +91,8 @@ pub struct LanguageSettings {
|
|||
pub extend_comment_on_newline: bool,
|
||||
/// Inlay hint related settings.
|
||||
pub inlay_hints: InlayHintSettings,
|
||||
/// Whether to automatically close brackets.
|
||||
pub use_autoclose: bool,
|
||||
}
|
||||
|
||||
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
||||
|
@ -208,6 +210,11 @@ pub struct LanguageSettingsContent {
|
|||
/// Inlay hint related settings.
|
||||
#[serde(default)]
|
||||
pub inlay_hints: Option<InlayHintSettings>,
|
||||
/// Whether to automatically type closing characters for you. For example,
|
||||
/// when you type (, Zed will automatically add a closing ) at the correct position.
|
||||
///
|
||||
/// Default: true
|
||||
pub use_autoclose: Option<bool>,
|
||||
}
|
||||
|
||||
/// The contents of the GitHub Copilot settings.
|
||||
|
@ -421,7 +428,7 @@ impl settings::Settings for AllLanguageSettings {
|
|||
let mut languages = HashMap::default();
|
||||
for (language_name, settings) in &default_value.languages {
|
||||
let mut language_settings = defaults.clone();
|
||||
merge_settings(&mut language_settings, &settings);
|
||||
merge_settings(&mut language_settings, settings);
|
||||
languages.insert(language_name.clone(), language_settings);
|
||||
}
|
||||
|
||||
|
@ -461,7 +468,7 @@ impl settings::Settings for AllLanguageSettings {
|
|||
languages
|
||||
.entry(language_name.clone())
|
||||
.or_insert_with(|| defaults.clone()),
|
||||
&user_language_settings,
|
||||
user_language_settings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -540,6 +547,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
|||
merge(&mut settings.tab_size, src.tab_size);
|
||||
merge(&mut settings.hard_tabs, src.hard_tabs);
|
||||
merge(&mut settings.soft_wrap, src.soft_wrap);
|
||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
|
||||
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ pub async fn parse_markdown_block(
|
|||
let mut current_language = None;
|
||||
let mut list_stack = Vec::new();
|
||||
|
||||
for event in Parser::new_ext(&markdown, Options::all()) {
|
||||
for event in Parser::new_ext(markdown, Options::all()) {
|
||||
let prev_len = text.len();
|
||||
match event {
|
||||
Event::Text(t) => {
|
||||
|
|
|
@ -283,7 +283,7 @@ impl SyntaxSnapshot {
|
|||
depth,
|
||||
position: edit_range.start,
|
||||
};
|
||||
if target.cmp(&cursor.start(), text).is_gt() {
|
||||
if target.cmp(cursor.start(), text).is_gt() {
|
||||
let slice = cursor.slice(&target, Bias::Left, text);
|
||||
layers.append(slice, text);
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ impl SyntaxSnapshot {
|
|||
cursor.next(text);
|
||||
}
|
||||
|
||||
layers.append(cursor.suffix(&text), &text);
|
||||
layers.append(cursor.suffix(text), text);
|
||||
drop(cursor);
|
||||
self.layers = layers;
|
||||
}
|
||||
|
@ -433,7 +433,7 @@ impl SyntaxSnapshot {
|
|||
|
||||
let max_depth = self.layers.summary().max_depth;
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
||||
cursor.next(&text);
|
||||
cursor.next(text);
|
||||
let mut layers = SumTree::new();
|
||||
|
||||
let mut changed_regions = ChangeRegionSet::default();
|
||||
|
@ -471,17 +471,17 @@ impl SyntaxSnapshot {
|
|||
};
|
||||
|
||||
let mut done = cursor.item().is_none();
|
||||
while !done && position.cmp(&cursor.end(text), &text).is_gt() {
|
||||
while !done && position.cmp(&cursor.end(text), text).is_gt() {
|
||||
done = true;
|
||||
|
||||
let bounded_position = SyntaxLayerPositionBeforeChange {
|
||||
position: position.clone(),
|
||||
change: changed_regions.start_position(),
|
||||
};
|
||||
if bounded_position.cmp(&cursor.start(), &text).is_gt() {
|
||||
if bounded_position.cmp(cursor.start(), text).is_gt() {
|
||||
let slice = cursor.slice(&bounded_position, Bias::Left, text);
|
||||
if !slice.is_empty() {
|
||||
layers.append(slice, &text);
|
||||
layers.append(slice, text);
|
||||
if changed_regions.prune(cursor.end(text), text) {
|
||||
done = false;
|
||||
}
|
||||
|
@ -491,7 +491,7 @@ impl SyntaxSnapshot {
|
|||
while position.cmp(&cursor.end(text), text).is_gt() {
|
||||
let Some(layer) = cursor.item() else { break };
|
||||
|
||||
if changed_regions.intersects(&layer, text) {
|
||||
if changed_regions.intersects(layer, text) {
|
||||
if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
|
||||
log::trace!(
|
||||
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
|
||||
|
@ -529,7 +529,7 @@ impl SyntaxSnapshot {
|
|||
if layer.range.to_offset(text) == (step_start_byte..step_end_byte)
|
||||
&& layer.content.language_id() == step.language.id()
|
||||
{
|
||||
cursor.next(&text);
|
||||
cursor.next(text);
|
||||
} else {
|
||||
old_layer = None;
|
||||
}
|
||||
|
@ -561,7 +561,7 @@ impl SyntaxSnapshot {
|
|||
log::trace!(
|
||||
"existing layer. language:{}, start:{:?}, ranges:{:?}",
|
||||
language.name(),
|
||||
LogPoint(layer_start.to_point(&text)),
|
||||
LogPoint(layer_start.to_point(text)),
|
||||
LogIncludedRanges(&old_tree.included_ranges())
|
||||
);
|
||||
|
||||
|
@ -584,7 +584,7 @@ impl SyntaxSnapshot {
|
|||
insert_newlines_between_ranges(
|
||||
changed_indices,
|
||||
&mut included_ranges,
|
||||
&text,
|
||||
text,
|
||||
step_start_byte,
|
||||
step_start_point,
|
||||
);
|
||||
|
@ -701,7 +701,7 @@ impl SyntaxSnapshot {
|
|||
range: step.range,
|
||||
content,
|
||||
},
|
||||
&text,
|
||||
text,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -860,7 +860,7 @@ impl<'a> SyntaxMapCaptures<'a> {
|
|||
Some(grammar) => grammar,
|
||||
None => continue,
|
||||
};
|
||||
let query = match query(&grammar) {
|
||||
let query = match query(grammar) {
|
||||
Some(query) => query,
|
||||
None => continue,
|
||||
};
|
||||
|
@ -978,7 +978,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
|||
Some(grammar) => grammar,
|
||||
None => continue,
|
||||
};
|
||||
let query = match query(&grammar) {
|
||||
let query = match query(grammar) {
|
||||
Some(query) => query,
|
||||
None => continue,
|
||||
};
|
||||
|
@ -1087,7 +1087,7 @@ impl<'a> SyntaxMapMatchesLayer<'a> {
|
|||
fn advance(&mut self) {
|
||||
if let Some(mat) = self.matches.next() {
|
||||
self.next_captures.clear();
|
||||
self.next_captures.extend_from_slice(&mat.captures);
|
||||
self.next_captures.extend_from_slice(mat.captures);
|
||||
self.next_pattern_index = mat.pattern_index;
|
||||
self.has_next = true;
|
||||
} else {
|
||||
|
@ -1517,7 +1517,7 @@ impl Eq for ParseStep {}
|
|||
|
||||
impl PartialOrd for ParseStep {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@ impl LanguageServer {
|
|||
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
|
||||
handler(
|
||||
msg.id,
|
||||
&msg.params.map(|params| params.get()).unwrap_or("null"),
|
||||
msg.params.map(|params| params.get()).unwrap_or("null"),
|
||||
cx.clone(),
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -2316,7 +2316,7 @@ impl MultiBufferSnapshot {
|
|||
&self,
|
||||
point: T,
|
||||
) -> Option<(&BufferSnapshot, usize)> {
|
||||
let offset = point.to_offset(&self);
|
||||
let offset = point.to_offset(self);
|
||||
let mut cursor = self.excerpts.cursor::<usize>();
|
||||
cursor.seek(&offset, Bias::Right, &());
|
||||
if cursor.item().is_none() {
|
||||
|
@ -3694,7 +3694,7 @@ impl ExcerptId {
|
|||
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
|
||||
let a = snapshot.excerpt_locator_for_id(*self);
|
||||
let b = snapshot.excerpt_locator_for_id(*other);
|
||||
a.cmp(&b).then_with(|| self.0.cmp(&other.0))
|
||||
a.cmp(b).then_with(|| self.0.cmp(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1532,7 +1532,7 @@ impl LspCommand for GetCompletions {
|
|||
.iter()
|
||||
.map(language::proto::serialize_completion)
|
||||
.collect(),
|
||||
version: serialize_version(&buffer_version),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1672,7 +1672,7 @@ impl LspCommand for GetCodeActions {
|
|||
.iter()
|
||||
.map(language::proto::serialize_code_action)
|
||||
.collect(),
|
||||
version: serialize_version(&buffer_version),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,16 +34,16 @@ use gpui::{
|
|||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
|
||||
point_to_lsp,
|
||||
markdown, point_to_lsp,
|
||||
proto::{
|
||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||
serialize_anchor, serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
|
||||
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
|
||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
|
||||
LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
|
||||
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp::{
|
||||
|
@ -52,7 +52,7 @@ use lsp::{
|
|||
};
|
||||
use lsp_command::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use prettier_support::{DefaultPrettier, PrettierInstance};
|
||||
use project_settings::{LspSettings, ProjectSettings};
|
||||
|
@ -2947,7 +2947,7 @@ impl Project {
|
|||
};
|
||||
task.await;
|
||||
|
||||
this.update(&mut cx, |this, mut cx| {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let worktrees = this.worktrees.clone();
|
||||
for worktree in worktrees {
|
||||
let worktree = match worktree.upgrade() {
|
||||
|
@ -2962,7 +2962,7 @@ impl Project {
|
|||
root_path,
|
||||
adapter.clone(),
|
||||
language.clone(),
|
||||
&mut cx,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
|
@ -3544,8 +3544,8 @@ impl Project {
|
|||
if errored {
|
||||
log::warn!("test binary check failed");
|
||||
let task = this
|
||||
.update(&mut cx, move |this, mut cx| {
|
||||
this.reinstall_language_server(language, adapter, server_id, &mut cx)
|
||||
.update(&mut cx, move |this, cx| {
|
||||
this.reinstall_language_server(language, adapter, server_id, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
@ -3768,8 +3768,7 @@ impl Project {
|
|||
}
|
||||
};
|
||||
if let Some(relative_glob_pattern) = relative_glob_pattern {
|
||||
let literal_prefix =
|
||||
glob_literal_prefix(&relative_glob_pattern);
|
||||
let literal_prefix = glob_literal_prefix(relative_glob_pattern);
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
|
||||
|
@ -4231,9 +4230,9 @@ impl Project {
|
|||
format_operation = Some(FormatOperation::Lsp(
|
||||
Self::format_via_lsp(
|
||||
&project,
|
||||
&buffer,
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&language_server,
|
||||
language_server,
|
||||
tab_size,
|
||||
&mut cx,
|
||||
)
|
||||
|
@ -4252,8 +4251,8 @@ impl Project {
|
|||
format_operation = Self::format_via_external_command(
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&command,
|
||||
&arguments,
|
||||
command,
|
||||
arguments,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
|
@ -4276,9 +4275,9 @@ impl Project {
|
|||
format_operation = Some(FormatOperation::Lsp(
|
||||
Self::format_via_lsp(
|
||||
&project,
|
||||
&buffer,
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&language_server,
|
||||
language_server,
|
||||
tab_size,
|
||||
&mut cx,
|
||||
)
|
||||
|
@ -4828,6 +4827,170 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve_completions(
|
||||
&self,
|
||||
completion_indices: Vec<usize>,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<bool>> {
|
||||
let client = self.client();
|
||||
let language_registry = self.languages().clone();
|
||||
|
||||
let is_remote = self.is_remote();
|
||||
let project_id = self.remote_id();
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let mut did_resolve = false;
|
||||
if is_remote {
|
||||
let project_id =
|
||||
project_id.ok_or_else(|| anyhow!("Remote project without remote_id"))?;
|
||||
|
||||
for completion_index in completion_indices {
|
||||
let completions_guard = completions.read();
|
||||
let completion = &completions_guard[completion_index];
|
||||
if completion.documentation.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
did_resolve = true;
|
||||
let server_id = completion.server_id;
|
||||
let completion = completion.lsp_completion.clone();
|
||||
drop(completions_guard);
|
||||
|
||||
Self::resolve_completion_documentation_remote(
|
||||
project_id,
|
||||
server_id,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
completion,
|
||||
client.clone(),
|
||||
language_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
for completion_index in completion_indices {
|
||||
let completions_guard = completions.read();
|
||||
let completion = &completions_guard[completion_index];
|
||||
if completion.documentation.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let server_id = completion.server_id;
|
||||
let completion = completion.lsp_completion.clone();
|
||||
drop(completions_guard);
|
||||
|
||||
let server = this
|
||||
.read_with(&mut cx, |project, _| {
|
||||
project.language_server_for_id(server_id)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(server) = server else {
|
||||
continue;
|
||||
};
|
||||
|
||||
did_resolve = true;
|
||||
Self::resolve_completion_documentation_local(
|
||||
server,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
completion,
|
||||
language_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(did_resolve)
|
||||
})
|
||||
}
|
||||
|
||||
async fn resolve_completion_documentation_local(
|
||||
server: Arc<lsp::LanguageServer>,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
completion_index: usize,
|
||||
completion: lsp::CompletionItem,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
) {
|
||||
let can_resolve = server
|
||||
.capabilities()
|
||||
.completion_provider
|
||||
.as_ref()
|
||||
.and_then(|options| options.resolve_provider)
|
||||
.unwrap_or(false);
|
||||
if !can_resolve {
|
||||
return;
|
||||
}
|
||||
|
||||
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
|
||||
let Some(completion_item) = request.await.log_err() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(lsp_documentation) = completion_item.documentation {
|
||||
let documentation = language::prepare_completion_documentation(
|
||||
&lsp_documentation,
|
||||
&language_registry,
|
||||
None, // TODO: Try to reasonably work out which language the completion is for
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
} else {
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(Documentation::Undocumented);
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_completion_documentation_remote(
|
||||
project_id: u64,
|
||||
server_id: LanguageServerId,
|
||||
completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
completion_index: usize,
|
||||
completion: lsp::CompletionItem,
|
||||
client: Arc<Client>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
) {
|
||||
let request = proto::ResolveCompletionDocumentation {
|
||||
project_id,
|
||||
language_server_id: server_id.0 as u64,
|
||||
lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
|
||||
};
|
||||
|
||||
let Some(response) = client
|
||||
.request(request)
|
||||
.await
|
||||
.context("completion documentation resolve proto request")
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if response.text.is_empty() {
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(Documentation::Undocumented);
|
||||
}
|
||||
|
||||
let documentation = if response.is_markdown {
|
||||
Documentation::MultiLineMarkdown(
|
||||
markdown::parse_markdown(&response.text, &language_registry, None).await,
|
||||
)
|
||||
} else if response.text.lines().count() <= 1 {
|
||||
Documentation::SingleLine(response.text)
|
||||
} else {
|
||||
Documentation::MultiLinePlainText(response.text)
|
||||
};
|
||||
|
||||
let mut completions = completions.write();
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
}
|
||||
|
||||
pub fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
|
@ -5683,7 +5846,7 @@ impl Project {
|
|||
snapshot.file().map(|file| file.path().as_ref()),
|
||||
) {
|
||||
query
|
||||
.search(&snapshot, None)
|
||||
.search(snapshot, None)
|
||||
.await
|
||||
.iter()
|
||||
.map(|range| {
|
||||
|
@ -6526,7 +6689,7 @@ impl Project {
|
|||
snapshot.repository_and_work_directory_for_path(&path)?;
|
||||
let repo = snapshot.get_local_repo(&repo)?;
|
||||
let relative_path = path.strip_prefix(&work_directory).ok()?;
|
||||
let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
|
||||
let base_text = repo.repo_ptr.lock().load_index_text(relative_path);
|
||||
Some((buffer, base_text))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -6659,7 +6822,7 @@ impl Project {
|
|||
for (_, _, path_summary) in
|
||||
self.diagnostic_summaries(include_ignored, cx)
|
||||
.filter(|(path, _, _)| {
|
||||
let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
|
||||
let worktree = self.entry_for_path(path, cx).map(|entry| entry.is_ignored);
|
||||
include_ignored || worktree == Some(false)
|
||||
})
|
||||
{
|
||||
|
@ -6685,7 +6848,7 @@ impl Project {
|
|||
})
|
||||
})
|
||||
.filter(move |(path, _, _)| {
|
||||
let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
|
||||
let worktree = self.entry_for_path(path, cx).map(|entry| entry.is_ignored);
|
||||
include_ignored || worktree == Some(false)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ impl Project {
|
|||
|
||||
if let Some(python_settings) = &python_settings.as_option() {
|
||||
let activate_script_path =
|
||||
self.find_activate_script_path(&python_settings, working_directory);
|
||||
self.find_activate_script_path(python_settings, working_directory);
|
||||
self.activate_python_virtual_environment(
|
||||
activate_script_path,
|
||||
&terminal_handle,
|
||||
|
|
|
@ -1437,7 +1437,7 @@ impl LocalWorktree {
|
|||
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
|
||||
project_id,
|
||||
worktree_id: cx.entity_id().as_u64(),
|
||||
summary: Some(summary.to_proto(server_id, &path)),
|
||||
summary: Some(summary.to_proto(server_id, path)),
|
||||
}) {
|
||||
return Task::ready(Err(e));
|
||||
}
|
||||
|
@ -2309,7 +2309,7 @@ impl LocalSnapshot {
|
|||
impl BackgroundScannerState {
|
||||
fn should_scan_directory(&self, entry: &Entry) -> bool {
|
||||
(!entry.is_external && !entry.is_ignored)
|
||||
|| entry.path.file_name() == Some(&*DOT_GIT)
|
||||
|| entry.path.file_name() == Some(*DOT_GIT)
|
||||
|| self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
|
||||
|| self
|
||||
.paths_to_scan
|
||||
|
@ -3374,7 +3374,7 @@ impl BackgroundScanner {
|
|||
let mut is_git_related = false;
|
||||
if let Some(dot_git_dir) = abs_path
|
||||
.ancestors()
|
||||
.find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
|
||||
.find(|ancestor| ancestor.file_name() == Some(*DOT_GIT))
|
||||
{
|
||||
let dot_git_path = dot_git_dir
|
||||
.strip_prefix(&root_canonical_path)
|
||||
|
@ -3772,7 +3772,7 @@ impl BackgroundScanner {
|
|||
for entry in &mut new_entries {
|
||||
state.reuse_entry_id(entry);
|
||||
if entry.is_dir() {
|
||||
if state.should_scan_directory(&entry) {
|
||||
if state.should_scan_directory(entry) {
|
||||
job_ix += 1;
|
||||
} else {
|
||||
log::debug!("defer scanning directory {:?}", entry.path);
|
||||
|
@ -3814,9 +3814,9 @@ impl BackgroundScanner {
|
|||
abs_paths
|
||||
.iter()
|
||||
.map(|abs_path| async move {
|
||||
let metadata = self.fs.metadata(&abs_path).await?;
|
||||
let metadata = self.fs.metadata(abs_path).await?;
|
||||
if let Some(metadata) = metadata {
|
||||
let canonical_path = self.fs.canonicalize(&abs_path).await?;
|
||||
let canonical_path = self.fs.canonicalize(abs_path).await?;
|
||||
anyhow::Ok(Some((metadata, canonical_path)))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
@ -3864,7 +3864,7 @@ impl BackgroundScanner {
|
|||
fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
|
||||
|
||||
if !is_dir && !fs_entry.is_ignored {
|
||||
if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
|
||||
if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(path) {
|
||||
if let Ok(repo_path) = path.strip_prefix(work_dir.0) {
|
||||
let repo_path = RepoPath(repo_path.into());
|
||||
let repo = repo.repo_ptr.lock();
|
||||
|
@ -3884,7 +3884,7 @@ impl BackgroundScanner {
|
|||
state.insert_entry(fs_entry, self.fs.as_ref());
|
||||
}
|
||||
Ok(None) => {
|
||||
self.remove_repo_path(&path, &mut state.snapshot);
|
||||
self.remove_repo_path(path, &mut state.snapshot);
|
||||
}
|
||||
Err(err) => {
|
||||
// TODO - create a special 'error' entry in the entries tree to mark this
|
||||
|
|
|
@ -21,6 +21,7 @@ sum_tree = { path = "../sum_tree" }
|
|||
theme = { path = "../theme" }
|
||||
language = { path = "../language" }
|
||||
util = { path = "../util" }
|
||||
ui = { path = "../ui" }
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
|
|
|
@ -6,6 +6,7 @@ use gpui::{
|
|||
use language::{HighlightId, Language, LanguageRegistry};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ActiveTheme;
|
||||
use ui::LinkPreview;
|
||||
use util::RangeExt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
@ -64,7 +65,7 @@ impl RichText {
|
|||
},
|
||||
Highlight::Id(id) => HighlightStyle {
|
||||
background_color: Some(code_background),
|
||||
..id.style(&theme.syntax()).unwrap_or_default()
|
||||
..id.style(theme.syntax()).unwrap_or_default()
|
||||
},
|
||||
Highlight::Highlight(highlight) => *highlight,
|
||||
Highlight::Mention => HighlightStyle {
|
||||
|
@ -84,6 +85,18 @@ impl RichText {
|
|||
let link_urls = self.link_urls.clone();
|
||||
move |ix, cx| cx.open_url(&link_urls[ix])
|
||||
})
|
||||
.tooltip({
|
||||
let link_ranges = self.link_ranges.clone();
|
||||
let link_urls = self.link_urls.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&link_urls[ix], cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +120,7 @@ pub fn render_markdown_mut(
|
|||
let mut list_stack = Vec::new();
|
||||
|
||||
let options = Options::all();
|
||||
for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
|
||||
for (event, source_range) in Parser::new_ext(block, options).into_offset_iter() {
|
||||
let prev_len = text.len();
|
||||
match event {
|
||||
Event::Text(t) => {
|
||||
|
@ -237,7 +250,7 @@ pub fn render_markdown_mut(
|
|||
_ => {}
|
||||
},
|
||||
Event::HardBreak => text.push('\n'),
|
||||
Event::SoftBreak => text.push(' '),
|
||||
Event::SoftBreak => text.push('\n'),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -533,7 +533,7 @@ impl SettingsStore {
|
|||
}
|
||||
|
||||
if let Some(local_settings) =
|
||||
setting_value.deserialize_setting(&local_settings).log_err()
|
||||
setting_value.deserialize_setting(local_settings).log_err()
|
||||
{
|
||||
paths_stack.push(Some((*root_id, path.as_ref())));
|
||||
user_settings_stack.push(local_settings);
|
||||
|
@ -697,8 +697,7 @@ fn update_value_in_json_text<'a>(
|
|||
if let Some(new_object) = new_value.as_object_mut() {
|
||||
new_object.retain(|_, v| !v.is_null());
|
||||
}
|
||||
let (range, replacement) =
|
||||
replace_value_in_json_text(text, &key_path, tab_size, &new_value);
|
||||
let (range, replacement) = replace_value_in_json_text(text, key_path, tab_size, &new_value);
|
||||
text.replace_range(range.clone(), &replacement);
|
||||
edits.push((range, replacement));
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ impl StoryContainer {
|
|||
}
|
||||
|
||||
impl ParentElement for StoryContainer {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,7 +372,7 @@ impl RenderOnce for StorySection {
|
|||
}
|
||||
|
||||
impl ParentElement for StorySection {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue