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",
|
"log",
|
||||||
"menu",
|
"menu",
|
||||||
"notifications",
|
"notifications",
|
||||||
|
"parking_lot 0.11.2",
|
||||||
"picker",
|
"picker",
|
||||||
"postage",
|
"postage",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -6144,6 +6145,7 @@ dependencies = [
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
"theme",
|
"theme",
|
||||||
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -9079,6 +9081,7 @@ dependencies = [
|
||||||
"nvim-rs",
|
"nvim-rs",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"project",
|
"project",
|
||||||
|
"regex",
|
||||||
"search",
|
"search",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
|
|
@ -183,6 +183,7 @@
|
||||||
"context": "Editor && mode == auto_height",
|
"context": "Editor && mode == auto_height",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-enter": "editor::Newline",
|
"ctrl-enter": "editor::Newline",
|
||||||
|
"shift-enter": "editor::Newline",
|
||||||
"ctrl-shift-enter": "editor::NewlineBelow"
|
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -104,8 +104,6 @@
|
||||||
"shift-v": "vim::ToggleVisualLine",
|
"shift-v": "vim::ToggleVisualLine",
|
||||||
"ctrl-v": "vim::ToggleVisualBlock",
|
"ctrl-v": "vim::ToggleVisualBlock",
|
||||||
"ctrl-q": "vim::ToggleVisualBlock",
|
"ctrl-q": "vim::ToggleVisualBlock",
|
||||||
"*": "vim::MoveToNext",
|
|
||||||
"#": "vim::MoveToPrev",
|
|
||||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||||
"ctrl-f": "vim::PageDown",
|
"ctrl-f": "vim::PageDown",
|
||||||
"pagedown": "vim::PageDown",
|
"pagedown": "vim::PageDown",
|
||||||
|
@ -329,6 +327,8 @@
|
||||||
"backwards": true
|
"backwards": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"*": "vim::MoveToNext",
|
||||||
|
"#": "vim::MoveToPrev",
|
||||||
";": "vim::RepeatFind",
|
";": "vim::RepeatFind",
|
||||||
",": [
|
",": [
|
||||||
"vim::RepeatFind",
|
"vim::RepeatFind",
|
||||||
|
@ -421,6 +421,18 @@
|
||||||
"shift-r": "vim::SubstituteLine",
|
"shift-r": "vim::SubstituteLine",
|
||||||
"c": "vim::Substitute",
|
"c": "vim::Substitute",
|
||||||
"~": "vim::ChangeCase",
|
"~": "vim::ChangeCase",
|
||||||
|
"*": [
|
||||||
|
"vim::MoveToNext",
|
||||||
|
{
|
||||||
|
"partialWord": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"#": [
|
||||||
|
"vim::MoveToPrev",
|
||||||
|
{
|
||||||
|
"partialWord": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"ctrl-a": "vim::Increment",
|
"ctrl-a": "vim::Increment",
|
||||||
"ctrl-x": "vim::Decrement",
|
"ctrl-x": "vim::Decrement",
|
||||||
"g ctrl-a": [
|
"g ctrl-a": [
|
||||||
|
|
|
@ -72,6 +72,9 @@
|
||||||
// Whether to use additional LSP queries to format (and amend) the code after
|
// Whether to use additional LSP queries to format (and amend) the code after
|
||||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||||
"use_on_type_format": true,
|
"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
|
// Controls whether copilot provides suggestion immediately
|
||||||
// or waits for a `copilot::Toggle`
|
// or waits for a `copilot::Toggle`
|
||||||
"show_copilot_suggestions": true,
|
"show_copilot_suggestions": true,
|
||||||
|
|
|
@ -134,7 +134,7 @@ pub async fn stream_completion(
|
||||||
line: Result<String, io::Error>,
|
line: Result<String, io::Error>,
|
||||||
) -> Result<Option<OpenAIResponseStreamEvent>> {
|
) -> Result<Option<OpenAIResponseStreamEvent>> {
|
||||||
if let Some(data) = line?.strip_prefix("data: ") {
|
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))
|
Ok(Some(event))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
@ -1432,7 +1432,7 @@ impl Room {
|
||||||
let display = displays
|
let display = displays
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("no display found"))?;
|
.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()
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.update(&mut cx, |this, _| {
|
.update(&mut cx, |this, _| {
|
||||||
|
|
|
@ -86,11 +86,11 @@ pub struct ChannelPathsInsertGuard<'a> {
|
||||||
|
|
||||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
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) {
|
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 {
|
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||||
|
@ -131,8 +131,8 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.channels_ordered.sort_by(|a, b| {
|
self.channels_ordered.sort_by(|a, b| {
|
||||||
let a = channel_path_sorting_key(*a, &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);
|
let b = channel_path_sorting_key(*b, self.channels_by_id);
|
||||||
a.cmp(b)
|
a.cmp(b)
|
||||||
});
|
});
|
||||||
self.channels_ordered.dedup();
|
self.channels_ordered.dedup();
|
||||||
|
@ -167,7 +167,7 @@ fn insert_note_changed(
|
||||||
if epoch > unseen_version.0 {
|
if epoch > unseen_version.0 {
|
||||||
*unseen_version = (epoch, version.clone());
|
*unseen_version = (epoch, version.clone());
|
||||||
} else {
|
} else {
|
||||||
unseen_version.1.join(&version);
|
unseen_version.1.join(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1310,7 +1310,7 @@ impl Client {
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
if let Some(handler) = handler {
|
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();
|
let client_id = self.id();
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
|
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
|
||||||
|
|
|
@ -38,8 +38,9 @@ struct TelemetryState {
|
||||||
flush_events_task: Option<Task<()>>,
|
flush_events_task: Option<Task<()>>,
|
||||||
log_file: Option<NamedTempFile>,
|
log_file: Option<NamedTempFile>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
first_event_datetime: Option<DateTime<Utc>>,
|
first_event_date_time: Option<DateTime<Utc>>,
|
||||||
event_coalescer: EventCoalescer,
|
event_coalescer: EventCoalescer,
|
||||||
|
max_queue_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EVENTS_URL_PATH: &'static str = "/api/events";
|
const EVENTS_URL_PATH: &'static str = "/api/events";
|
||||||
|
@ -69,14 +70,14 @@ struct EventWrapper {
|
||||||
event: Event,
|
event: Event,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum AssistantKind {
|
pub enum AssistantKind {
|
||||||
Panel,
|
Panel,
|
||||||
Inline,
|
Inline,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Editor {
|
Editor {
|
||||||
|
@ -168,8 +169,9 @@ impl Telemetry {
|
||||||
flush_events_task: None,
|
flush_events_task: None,
|
||||||
log_file: None,
|
log_file: None,
|
||||||
is_staff: None,
|
is_staff: None,
|
||||||
first_event_datetime: None,
|
first_event_date_time: None,
|
||||||
event_coalescer: EventCoalescer::new(),
|
event_coalescer: EventCoalescer::new(),
|
||||||
|
max_queue_size: MAX_QUEUE_LEN,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
|
@ -310,7 +312,7 @@ impl Telemetry {
|
||||||
operation,
|
operation,
|
||||||
copilot_enabled,
|
copilot_enabled,
|
||||||
copilot_enabled_for_language,
|
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)
|
self.report_event(event)
|
||||||
|
@ -326,7 +328,7 @@ impl Telemetry {
|
||||||
suggestion_id,
|
suggestion_id,
|
||||||
suggestion_accepted,
|
suggestion_accepted,
|
||||||
file_extension,
|
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)
|
self.report_event(event)
|
||||||
|
@ -342,7 +344,7 @@ impl Telemetry {
|
||||||
conversation_id,
|
conversation_id,
|
||||||
kind,
|
kind,
|
||||||
model,
|
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)
|
self.report_event(event)
|
||||||
|
@ -358,7 +360,7 @@ impl Telemetry {
|
||||||
operation,
|
operation,
|
||||||
room_id,
|
room_id,
|
||||||
channel_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)
|
self.report_event(event)
|
||||||
|
@ -368,7 +370,7 @@ impl Telemetry {
|
||||||
let event = Event::Cpu {
|
let event = Event::Cpu {
|
||||||
usage_as_percentage,
|
usage_as_percentage,
|
||||||
core_count,
|
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)
|
self.report_event(event)
|
||||||
|
@ -382,26 +384,36 @@ impl Telemetry {
|
||||||
let event = Event::Memory {
|
let event = Event::Memory {
|
||||||
memory_in_bytes,
|
memory_in_bytes,
|
||||||
virtual_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)
|
self.report_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_app_event(self: &Arc<Self>, operation: String) {
|
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 {
|
let event = Event::App {
|
||||||
operation,
|
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) {
|
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
|
||||||
let event = Event::Setting {
|
let event = Event::Setting {
|
||||||
setting,
|
setting,
|
||||||
value,
|
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)
|
self.report_event(event)
|
||||||
|
@ -416,7 +428,7 @@ impl Telemetry {
|
||||||
let event = Event::Edit {
|
let event = Event::Edit {
|
||||||
duration: end.timestamp_millis() - start.timestamp_millis(),
|
duration: end.timestamp_millis() - start.timestamp_millis(),
|
||||||
environment,
|
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);
|
self.report_event(event);
|
||||||
|
@ -427,22 +439,21 @@ impl Telemetry {
|
||||||
let event = Event::Action {
|
let event = Event::Action {
|
||||||
source,
|
source,
|
||||||
action,
|
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)
|
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();
|
let mut state = self.state.lock();
|
||||||
|
|
||||||
match state.first_event_datetime {
|
match state.first_event_date_time {
|
||||||
Some(first_event_datetime) => {
|
Some(first_event_date_time) => {
|
||||||
let now: DateTime<Utc> = Utc::now();
|
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
|
||||||
now.timestamp_millis() - first_event_datetime.timestamp_millis()
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
state.first_event_datetime = Some(Utc::now());
|
state.first_event_date_time = Some(date_time);
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,7 +479,7 @@ impl Telemetry {
|
||||||
state.events_queue.push(EventWrapper { signed_in, event });
|
state.events_queue.push(EventWrapper { signed_in, event });
|
||||||
|
|
||||||
if state.installation_id.is_some() {
|
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);
|
drop(state);
|
||||||
self.flush_events();
|
self.flush_events();
|
||||||
}
|
}
|
||||||
|
@ -489,7 +500,7 @@ impl Telemetry {
|
||||||
|
|
||||||
pub fn flush_events(self: &Arc<Self>) {
|
pub fn flush_events(self: &Arc<Self>) {
|
||||||
let mut state = self.state.lock();
|
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);
|
let mut events = mem::take(&mut state.events_queue);
|
||||||
state.flush_events_task.take();
|
state.flush_events_task.take();
|
||||||
drop(state);
|
drop(state);
|
||||||
|
@ -548,3 +559,159 @@ impl Telemetry {
|
||||||
.detach();
|
.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
|
futures.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
@ -18,7 +18,7 @@ use project::Fs;
|
||||||
use rich_text::RichText;
|
use rich_text::RichText;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{
|
use ui::{
|
||||||
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
|
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 last_message = active_chat.message(ix.saturating_sub(1));
|
||||||
let this_message = active_chat.message(ix).clone();
|
let this_message = active_chat.message(ix).clone();
|
||||||
|
|
||||||
let is_continuation_from_previous = last_message.id != this_message.id
|
let duration_since_last_message = this_message.timestamp - last_message.timestamp;
|
||||||
&& last_message.sender.id == this_message.sender.id;
|
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 let ChannelMessageId::Saved(id) = this_message.id {
|
||||||
if this_message
|
if this_message
|
||||||
|
@ -325,8 +328,6 @@ impl ChatPanel {
|
||||||
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
|
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 belongs_to_user = Some(message.sender.id) == self.client.user_id();
|
||||||
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
|
||||||
(message.id, belongs_to_user || is_admin)
|
(message.id, belongs_to_user || is_admin)
|
||||||
|
@ -349,23 +350,21 @@ impl ChatPanel {
|
||||||
.when(!is_continuation_from_previous, |this| {
|
.when(!is_continuation_from_previous, |this| {
|
||||||
this.pt_3().child(
|
this.pt_3().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.child(
|
.text_ui_sm()
|
||||||
div().absolute().child(
|
.child(div().absolute().child(
|
||||||
Avatar::new(message.sender.avatar_uri.clone())
|
Avatar::new(message.sender.avatar_uri.clone()).size(cx.rem_size()),
|
||||||
.size(cx.rem_size() * 1.5),
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.pl(cx.rem_size() * 1.5 + px(6.0))
|
.pl(cx.rem_size() + px(6.0))
|
||||||
.pr(px(8.0))
|
.pr(px(8.0))
|
||||||
.font_weight(FontWeight::BOLD)
|
.font_weight(FontWeight::BOLD)
|
||||||
.child(Label::new(message.sender.github_login.clone())),
|
.child(Label::new(message.sender.github_login.clone())),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Label::new(format_timestamp(
|
Label::new(format_timestamp(
|
||||||
|
OffsetDateTime::now_utc(),
|
||||||
message.timestamp,
|
message.timestamp,
|
||||||
now,
|
|
||||||
self.local_timezone,
|
self.local_timezone,
|
||||||
))
|
))
|
||||||
.size(LabelSize::Small)
|
.size(LabelSize::Small)
|
||||||
|
@ -597,7 +596,7 @@ impl Render for ChatPanel {
|
||||||
el.child(
|
el.child(
|
||||||
div()
|
div()
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.h_7()
|
.h_6()
|
||||||
.w_full()
|
.w_full()
|
||||||
.bg(cx.theme().colors().editor_background),
|
.bg(cx.theme().colors().editor_background),
|
||||||
)
|
)
|
||||||
|
@ -671,28 +670,44 @@ impl Panel for ChatPanel {
|
||||||
impl EventEmitter<PanelEvent> for ChatPanel {}
|
impl EventEmitter<PanelEvent> for ChatPanel {}
|
||||||
|
|
||||||
fn format_timestamp(
|
fn format_timestamp(
|
||||||
mut timestamp: OffsetDateTime,
|
reference: OffsetDateTime,
|
||||||
mut now: OffsetDateTime,
|
timestamp: OffsetDateTime,
|
||||||
local_timezone: UtcOffset,
|
timezone: UtcOffset,
|
||||||
) -> String {
|
) -> String {
|
||||||
timestamp = timestamp.to_offset(local_timezone);
|
let timestamp_local = timestamp.to_offset(timezone);
|
||||||
now = now.to_offset(local_timezone);
|
let timestamp_local_hour = timestamp_local.hour();
|
||||||
|
|
||||||
let today = now.date();
|
let hour_12 = match timestamp_local_hour {
|
||||||
let date = timestamp.date();
|
0 => 12, // Midnight
|
||||||
let mut hour = timestamp.hour();
|
13..=23 => timestamp_local_hour - 12, // PM hours
|
||||||
let mut part = "am";
|
_ => timestamp_local_hour, // AM hours
|
||||||
if hour > 12 {
|
};
|
||||||
hour -= 12;
|
let meridiem = if timestamp_local_hour >= 12 {
|
||||||
part = "pm";
|
"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)
|
|
||||||
} else {
|
} 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)]
|
#[cfg(test)]
|
||||||
|
@ -701,6 +716,7 @@ mod tests {
|
||||||
use gpui::HighlightStyle;
|
use gpui::HighlightStyle;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rich_text::Highlight;
|
use rich_text::Highlight;
|
||||||
|
use time::{Date, OffsetDateTime, Time, UtcOffset};
|
||||||
use util::test::marked_text_ranges;
|
use util::test::marked_text_ranges;
|
||||||
|
|
||||||
#[gpui::test]
|
#[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 channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
|
||||||
use client::UserId;
|
use client::UserId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{AnchorRangeExt, Editor, EditorElement, EditorStyle};
|
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
|
||||||
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
|
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
|
||||||
Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
|
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 lazy_static::lazy_static;
|
||||||
|
use parking_lot::RwLock;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, UiTextSize};
|
||||||
|
|
||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
@ -31,6 +36,43 @@ pub struct MessageEditor {
|
||||||
channel_id: Option<ChannelId>,
|
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 {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
@ -38,8 +80,11 @@ impl MessageEditor {
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let this = cx.view().downgrade();
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, 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
|
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(
|
async fn find_mentions(
|
||||||
this: WeakView<MessageEditor>,
|
this: WeakView<MessageEditor>,
|
||||||
buffer: BufferSnapshot,
|
buffer: BufferSnapshot,
|
||||||
|
@ -216,7 +326,7 @@ impl Render for MessageEditor {
|
||||||
},
|
},
|
||||||
font_family: settings.ui_font.family.clone(),
|
font_family: settings.ui_font.family.clone(),
|
||||||
font_features: settings.ui_font.features,
|
font_features: settings.ui_font.features,
|
||||||
font_size: rems(0.875).into(),
|
font_size: UiTextSize::Small.rems().into(),
|
||||||
font_weight: FontWeight::NORMAL,
|
font_weight: FontWeight::NORMAL,
|
||||||
font_style: FontStyle::Normal,
|
font_style: FontStyle::Normal,
|
||||||
line_height: relative(1.3).into(),
|
line_height: relative(1.3).into(),
|
||||||
|
|
|
@ -85,7 +85,14 @@ impl Render for CollabTitlebarItem {
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.children(self.render_project_host(cx))
|
.children(self.render_project_host(cx))
|
||||||
.child(self.render_project_name(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(
|
.when_some(
|
||||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||||
|this, ((current_user, peer_id), room)| {
|
|this, ((current_user, peer_id), room)| {
|
||||||
|
|
|
@ -37,8 +37,8 @@ impl RenderOnce for FacePile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for FacePile {
|
impl ParentElement for FacePile {
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||||
&mut self.faces
|
self.faces.extend(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ impl CollabNotification {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for CollabNotification {
|
impl ParentElement for CollabNotification {
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||||
&mut self.children
|
self.children.extend(elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -311,7 +311,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
let action = command.action;
|
let action = command.action;
|
||||||
cx.focus(&self.previous_focus_handle);
|
cx.focus(&self.previous_focus_handle);
|
||||||
cx.window_context()
|
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);
|
.detach_and_log_err(cx);
|
||||||
self.dismissed(cx);
|
self.dismissed(cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -974,7 +974,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||||
.browser_download_url;
|
.browser_download_url;
|
||||||
|
|
||||||
let mut response = http
|
let mut response = http
|
||||||
.get(&url, Default::default(), true)
|
.get(url, Default::default(), true)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
|
.map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
|
||||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
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 {
|
cx.spawn(|mut cx| async move {
|
||||||
task.await;
|
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
|
workspace
|
||||||
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
|
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
|
||||||
Status::Authorized => workspace.show_toast(
|
Status::Authorized => workspace.show_toast(
|
||||||
|
|
|
@ -1584,27 +1584,34 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
|
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
|
||||||
|
let editor_view = editor.clone();
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
||||||
snapshot
|
snapshot
|
||||||
.blocks_in_range(0..snapshot.max_point().row())
|
.blocks_in_range(0..snapshot.max_point().row())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(ix, (row, block))| {
|
.filter_map(|(ix, (row, block))| {
|
||||||
let name = match block {
|
let name: SharedString = match block {
|
||||||
TransformBlock::Custom(block) => block
|
TransformBlock::Custom(block) => cx.with_element_context({
|
||||||
|
let editor_view = editor_view.clone();
|
||||||
|
|cx| -> Option<SharedString> {
|
||||||
|
block
|
||||||
.render(&mut BlockContext {
|
.render(&mut BlockContext {
|
||||||
view_context: cx,
|
context: cx,
|
||||||
anchor_x: px(0.),
|
anchor_x: px(0.),
|
||||||
gutter_padding: px(0.),
|
gutter_padding: px(0.),
|
||||||
gutter_width: px(0.),
|
gutter_width: px(0.),
|
||||||
line_height: px(0.),
|
line_height: px(0.),
|
||||||
em_width: px(0.),
|
em_width: px(0.),
|
||||||
block_id: ix,
|
block_id: ix,
|
||||||
|
view: editor_view,
|
||||||
editor_style: &editor::EditorStyle::default(),
|
editor_style: &editor::EditorStyle::default(),
|
||||||
})
|
})
|
||||||
.inner_id()?
|
.inner_id()?
|
||||||
.try_into()
|
.try_into()
|
||||||
.ok()?,
|
.ok()
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
|
||||||
TransformBlock::ExcerptHeader {
|
TransformBlock::ExcerptHeader {
|
||||||
starts_new_buffer, ..
|
starts_new_buffer, ..
|
||||||
|
|
|
@ -4,7 +4,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
|
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
|
||||||
use collections::{Bound, HashMap, HashSet};
|
use collections::{Bound, HashMap, HashSet};
|
||||||
use gpui::{AnyElement, Pixels, ViewContext};
|
use gpui::{AnyElement, ElementContext, Pixels, View};
|
||||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -81,7 +81,8 @@ pub enum BlockStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockContext<'a, 'b> {
|
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 anchor_x: Pixels,
|
||||||
pub gutter_width: Pixels,
|
pub gutter_width: Pixels,
|
||||||
pub gutter_padding: Pixels,
|
pub gutter_padding: Pixels,
|
||||||
|
@ -933,16 +934,16 @@ impl BlockDisposition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for BlockContext<'a, '_> {
|
impl<'a> Deref for BlockContext<'a, '_> {
|
||||||
type Target = ViewContext<'a, Editor>;
|
type Target = ElementContext<'a>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.view_context
|
self.context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for BlockContext<'_, '_> {
|
impl DerefMut for BlockContext<'_, '_> {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
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 aho_corasick::AhoCorasick;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use blink_manager::BlinkManager;
|
use blink_manager::BlinkManager;
|
||||||
use client::{Client, Collaborator, ParticipantIndex};
|
use client::{Collaborator, ParticipantIndex};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
|
@ -57,9 +57,10 @@ use gpui::{
|
||||||
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||||
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||||
DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
||||||
HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
|
HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, ParentElement, Pixels,
|
||||||
ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
|
Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
|
||||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
|
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakView,
|
||||||
|
WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
|
@ -71,8 +72,7 @@ use language::{
|
||||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
|
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
|
||||||
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
|
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
|
||||||
Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
|
Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||||
SelectionGoal, TransactionId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
|
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
|
||||||
|
@ -88,7 +88,7 @@ use ordered_float::OrderedFloat;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
|
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rpc::proto::{self, *};
|
use rpc::proto::*;
|
||||||
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
|
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
|
||||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -365,6 +365,7 @@ pub struct Editor {
|
||||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
||||||
project: Option<Model<Project>>,
|
project: Option<Model<Project>>,
|
||||||
|
completion_provider: Option<Box<dyn CompletionProvider>>,
|
||||||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||||
blink_manager: Model<BlinkManager>,
|
blink_manager: Model<BlinkManager>,
|
||||||
show_cursor_names: bool,
|
show_cursor_names: bool,
|
||||||
|
@ -408,6 +409,7 @@ pub struct Editor {
|
||||||
style: Option<EditorStyle>,
|
style: Option<EditorStyle>,
|
||||||
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
||||||
show_copilot_suggestions: bool,
|
show_copilot_suggestions: bool,
|
||||||
|
use_autoclose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorSnapshot {
|
pub struct EditorSnapshot {
|
||||||
|
@ -731,85 +733,21 @@ impl CompletionsMenu {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(project) = editor.project.clone() else {
|
let Some(provider) = editor.completion_provider.as_ref() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = project.read(cx).client();
|
let resolve_task = provider.resolve_completions(
|
||||||
let language_registry = project.read(cx).languages().clone();
|
self.matches.iter().map(|m| m.candidate_id).collect(),
|
||||||
|
self.completions.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
let is_remote = project.read(cx).is_remote();
|
return Some(cx.spawn(move |this, mut cx| async move {
|
||||||
let project_id = project.read(cx).remote_id();
|
if let Some(true) = resolve_task.await.log_err() {
|
||||||
|
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attempt_resolve_selected_completion_documentation(
|
fn attempt_resolve_selected_completion_documentation(
|
||||||
|
@ -826,146 +764,16 @@ impl CompletionsMenu {
|
||||||
let Some(project) = project else {
|
let Some(project) = project else {
|
||||||
return;
|
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();
|
|
||||||
|
|
||||||
|
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 {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
Self::resolve_completion_documentation_remote(
|
if let Some(true) = resolve_task.await.log_err() {
|
||||||
project_id,
|
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||||
server_id,
|
}
|
||||||
completions.clone(),
|
|
||||||
completion_index,
|
|
||||||
completion,
|
|
||||||
client,
|
|
||||||
language_registry.clone(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
_ = this.update(&mut cx, |_, cx| cx.notify());
|
|
||||||
})
|
})
|
||||||
.detach();
|
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible(&self) -> bool {
|
fn visible(&self) -> bool {
|
||||||
|
@ -1574,6 +1382,7 @@ impl Editor {
|
||||||
ime_transaction: Default::default(),
|
ime_transaction: Default::default(),
|
||||||
active_diagnostics: None,
|
active_diagnostics: None,
|
||||||
soft_wrap_mode_override,
|
soft_wrap_mode_override,
|
||||||
|
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
||||||
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
||||||
project,
|
project,
|
||||||
blink_manager: blink_manager.clone(),
|
blink_manager: blink_manager.clone(),
|
||||||
|
@ -1603,6 +1412,7 @@ impl Editor {
|
||||||
keymap_context_layers: Default::default(),
|
keymap_context_layers: Default::default(),
|
||||||
input_enabled: true,
|
input_enabled: true,
|
||||||
read_only: false,
|
read_only: false,
|
||||||
|
use_autoclose: true,
|
||||||
leader_peer_id: None,
|
leader_peer_id: None,
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
hover_state: Default::default(),
|
hover_state: Default::default(),
|
||||||
|
@ -1806,6 +1616,10 @@ impl Editor {
|
||||||
self.collaboration_hub = Some(hub);
|
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> {
|
pub fn placeholder_text(&self) -> Option<&str> {
|
||||||
self.placeholder_text.as_deref()
|
self.placeholder_text.as_deref()
|
||||||
}
|
}
|
||||||
|
@ -1880,6 +1694,10 @@ impl Editor {
|
||||||
self.read_only = read_only;
|
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) {
|
pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
|
||||||
self.show_copilot_suggestions = show_copilot_suggestions;
|
self.show_copilot_suggestions = show_copilot_suggestions;
|
||||||
}
|
}
|
||||||
|
@ -2478,7 +2296,12 @@ impl Editor {
|
||||||
),
|
),
|
||||||
&bracket_pair.start[..prefix_len],
|
&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);
|
let anchor = snapshot.anchor_before(selection.end);
|
||||||
new_selections.push((selection.map(|_| anchor), text.len()));
|
new_selections.push((selection.map(|_| anchor), text.len()));
|
||||||
new_autoclose_regions.push((
|
new_autoclose_regions.push((
|
||||||
|
@ -3252,9 +3075,7 @@ impl Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let project = if let Some(project) = self.project.clone() {
|
let Some(provider) = self.completion_provider.as_ref() else {
|
||||||
project
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3270,9 +3091,7 @@ impl Editor {
|
||||||
};
|
};
|
||||||
|
|
||||||
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
|
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
|
||||||
let completions = project.update(cx, |project, cx| {
|
let completions = provider.completions(&buffer, buffer_position, cx);
|
||||||
project.completions(&buffer, buffer_position, cx)
|
|
||||||
});
|
|
||||||
|
|
||||||
let id = post_inc(&mut self.next_completion_id);
|
let id = post_inc(&mut self.next_completion_id);
|
||||||
let task = cx.spawn(|this, mut cx| {
|
let task = cx.spawn(|this, mut cx| {
|
||||||
|
@ -3381,6 +3200,7 @@ impl Editor {
|
||||||
let buffer_handle = completions_menu.buffer;
|
let buffer_handle = completions_menu.buffer;
|
||||||
let completions = completions_menu.completions.read();
|
let completions = completions_menu.completions.read();
|
||||||
let completion = completions.get(mat.candidate_id)?;
|
let completion = completions.get(mat.candidate_id)?;
|
||||||
|
cx.stop_propagation();
|
||||||
|
|
||||||
let snippet;
|
let snippet;
|
||||||
let text;
|
let text;
|
||||||
|
@ -3477,15 +3297,13 @@ impl Editor {
|
||||||
this.refresh_copilot_suggestions(true, cx);
|
this.refresh_copilot_suggestions(true, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let project = self.project.clone()?;
|
let provider = self.completion_provider.as_ref()?;
|
||||||
let apply_edits = project.update(cx, |project, cx| {
|
let apply_edits = provider.apply_additional_edits_for_completion(
|
||||||
project.apply_additional_edits_for_completion(
|
|
||||||
buffer_handle,
|
buffer_handle,
|
||||||
completion.clone(),
|
completion.clone(),
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
)
|
);
|
||||||
});
|
|
||||||
Some(cx.foreground_executor().spawn(async move {
|
Some(cx.foreground_executor().spawn(async move {
|
||||||
apply_edits.await?;
|
apply_edits.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -3572,7 +3390,7 @@ impl Editor {
|
||||||
let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
|
let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
|
||||||
|
|
||||||
let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
|
let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
|
||||||
cx.update(|_, cx| {
|
cx.update(|cx| {
|
||||||
entries.sort_unstable_by_key(|(buffer, _)| {
|
entries.sort_unstable_by_key(|(buffer, _)| {
|
||||||
buffer.read(cx).file().map(|f| f.path().clone())
|
buffer.read(cx).file().map(|f| f.path().clone())
|
||||||
});
|
});
|
||||||
|
@ -3907,7 +3725,7 @@ impl Editor {
|
||||||
self.show_cursor_names = true;
|
self.show_cursor_names = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.spawn(|this, mut cx| async move {
|
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.update(&mut cx, |this, cx| {
|
||||||
this.show_cursor_names = false;
|
this.show_cursor_names = false;
|
||||||
cx.notify()
|
cx.notify()
|
||||||
|
@ -4094,7 +3912,7 @@ impl Editor {
|
||||||
gutter_hovered: bool,
|
gutter_hovered: bool,
|
||||||
_line_height: Pixels,
|
_line_height: Pixels,
|
||||||
_gutter_margin: Pixels,
|
_gutter_margin: Pixels,
|
||||||
cx: &mut ViewContext<Self>,
|
editor_view: View<Editor>,
|
||||||
) -> Vec<Option<IconButton>> {
|
) -> Vec<Option<IconButton>> {
|
||||||
fold_data
|
fold_data
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -4104,14 +3922,19 @@ impl Editor {
|
||||||
.map(|(fold_status, buffer_row, active)| {
|
.map(|(fold_status, buffer_row, active)| {
|
||||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
||||||
IconButton::new(ix as usize, ui::IconName::ChevronDown)
|
IconButton::new(ix as usize, ui::IconName::ChevronDown)
|
||||||
.on_click(cx.listener(move |editor, _e, cx| match fold_status {
|
.on_click({
|
||||||
|
let view = editor_view.clone();
|
||||||
|
move |_e, cx| {
|
||||||
|
view.update(cx, |editor, cx| match fold_status {
|
||||||
FoldStatus::Folded => {
|
FoldStatus::Folded => {
|
||||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
}
|
}
|
||||||
FoldStatus::Foldable => {
|
FoldStatus::Foldable => {
|
||||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
.icon_color(ui::Color::Muted)
|
.icon_color(ui::Color::Muted)
|
||||||
.icon_size(ui::IconSize::Small)
|
.icon_size(ui::IconSize::Small)
|
||||||
.selected(fold_status == FoldStatus::Folded)
|
.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(
|
fn inlay_hint_settings(
|
||||||
location: Anchor,
|
location: Anchor,
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
@ -9300,7 +9183,7 @@ impl Render for Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler for Editor {
|
impl ViewInputHandler for Editor {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
@ -9697,10 +9580,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
|
||||||
.size(ButtonSize::Compact)
|
.size(ButtonSize::Compact)
|
||||||
.style(ButtonStyle::Transparent)
|
.style(ButtonStyle::Transparent)
|
||||||
.visible_on_hover(group_id)
|
.visible_on_hover(group_id)
|
||||||
.on_click(cx.listener({
|
.on_click({
|
||||||
let message = diagnostic.message.clone();
|
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)),
|
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|
|
@ -25,12 +25,12 @@ use collections::{BTreeMap, HashMap};
|
||||||
use git::diff::DiffHunkStatus;
|
use git::diff::DiffHunkStatus;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
|
div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
|
||||||
AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
|
AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle,
|
||||||
CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla,
|
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds,
|
||||||
InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton,
|
InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta,
|
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
|
||||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
|
SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
|
||||||
Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext,
|
TextStyle, View, ViewContext, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::language_settings::ShowWhitespaceSetting;
|
use language::language_settings::ShowWhitespaceSetting;
|
||||||
|
@ -330,7 +330,7 @@ impl EditorElement {
|
||||||
register_action(view, cx, Editor::display_cursor_names);
|
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({
|
cx.on_key_event({
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
move |event: &ModifiersChangedEvent, phase, cx| {
|
move |event: &ModifiersChangedEvent, phase, cx| {
|
||||||
|
@ -628,7 +628,7 @@ impl EditorElement {
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
layout: &LayoutState,
|
layout: &LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let bounds = gutter_bounds.union(&text_bounds);
|
let bounds = gutter_bounds.union(&text_bounds);
|
||||||
let scroll_top =
|
let scroll_top =
|
||||||
|
@ -711,7 +711,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let line_height = layout.position_map.line_height;
|
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 line_height = layout.position_map.line_height;
|
||||||
|
|
||||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||||
|
@ -886,7 +886,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let start_row = layout.visible_display_row_range.start;
|
let start_row = layout.visible_display_row_range.start;
|
||||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||||
|
@ -1153,7 +1153,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||||
let start_row = layout.visible_display_row_range.start;
|
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;
|
popover_origin.x = popover_origin.x + x_out_of_bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
|
||||||
cx.break_content_mask(|cx| {
|
cx.break_content_mask(|cx| {
|
||||||
hover_popover.draw(popover_origin, available_space, cx)
|
hover_popover.draw(popover_origin, available_space, cx)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
current_y = popover_origin.y - HOVER_POPOVER_GAP;
|
current_y = popover_origin.y - HOVER_POPOVER_GAP;
|
||||||
}
|
}
|
||||||
|
@ -1266,7 +1268,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if layout.mode != EditorMode::Full {
|
if layout.mode != EditorMode::Full {
|
||||||
return;
|
return;
|
||||||
|
@ -1510,7 +1512,7 @@ impl EditorElement {
|
||||||
layout: &LayoutState,
|
layout: &LayoutState,
|
||||||
content_origin: gpui::Point<Pixels>,
|
content_origin: gpui::Point<Pixels>,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let start_row = layout.visible_display_row_range.start;
|
let start_row = layout.visible_display_row_range.start;
|
||||||
let end_row = layout.visible_display_row_range.end;
|
let end_row = layout.visible_display_row_range.end;
|
||||||
|
@ -1562,7 +1564,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||||
let scroll_left = scroll_position.x * layout.position_map.em_width;
|
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| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
||||||
let style = self.style.clone();
|
let style = self.style.clone();
|
||||||
|
@ -2081,7 +2083,9 @@ impl EditorElement {
|
||||||
.width;
|
.width;
|
||||||
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.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(
|
self.layout_blocks(
|
||||||
start_row..end_row,
|
start_row..end_row,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
|
@ -2095,8 +2099,10 @@ impl EditorElement {
|
||||||
&style,
|
&style,
|
||||||
&line_layouts,
|
&line_layouts,
|
||||||
editor,
|
editor,
|
||||||
|
editor_view,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let scroll_max = point(
|
let scroll_max = point(
|
||||||
|
@ -2128,7 +2134,13 @@ impl EditorElement {
|
||||||
if let Some(newest_selection_head) = newest_selection_head {
|
if let Some(newest_selection_head) = newest_selection_head {
|
||||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||||
if editor.context_menu_visible() {
|
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 =
|
context_menu =
|
||||||
editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
|
editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
|
||||||
}
|
}
|
||||||
|
@ -2166,15 +2178,19 @@ impl EditorElement {
|
||||||
cx,
|
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(
|
editor.render_fold_indicators(
|
||||||
fold_statuses,
|
fold_statuses,
|
||||||
&style,
|
&style,
|
||||||
editor.gutter_hovered,
|
editor.gutter_hovered,
|
||||||
line_height,
|
line_height,
|
||||||
gutter_margin,
|
gutter_margin,
|
||||||
cx,
|
editor_view,
|
||||||
)
|
)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let invisible_symbol_font_size = font_size / 2.;
|
let invisible_symbol_font_size = font_size / 2.;
|
||||||
|
@ -2265,7 +2281,8 @@ impl EditorElement {
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
line_layouts: &[LineWithInvisibles],
|
line_layouts: &[LineWithInvisibles],
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
cx: &mut ViewContext<Editor>,
|
editor_view: View<Editor>,
|
||||||
|
cx: &mut ElementContext,
|
||||||
) -> (Pixels, Vec<BlockLayout>) {
|
) -> (Pixels, Vec<BlockLayout>) {
|
||||||
let mut block_id = 0;
|
let mut block_id = 0;
|
||||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||||
|
@ -2279,7 +2296,7 @@ impl EditorElement {
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
block_id: usize,
|
block_id: usize,
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
cx: &mut ViewContext<Editor>| {
|
cx: &mut ElementContext| {
|
||||||
let mut element = match block {
|
let mut element = match block {
|
||||||
TransformBlock::Custom(block) => {
|
TransformBlock::Custom(block) => {
|
||||||
let align_to = block
|
let align_to = block
|
||||||
|
@ -2298,13 +2315,14 @@ impl EditorElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
block.render(&mut BlockContext {
|
block.render(&mut BlockContext {
|
||||||
view_context: cx,
|
context: cx,
|
||||||
anchor_x,
|
anchor_x,
|
||||||
gutter_padding,
|
gutter_padding,
|
||||||
line_height,
|
line_height,
|
||||||
gutter_width,
|
gutter_width,
|
||||||
em_width,
|
em_width,
|
||||||
block_id,
|
block_id,
|
||||||
|
view: editor_view.clone(),
|
||||||
editor_style: &self.style,
|
editor_style: &self.style,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2496,7 +2514,7 @@ impl EditorElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
interactive_bounds: &InteractiveBounds,
|
interactive_bounds: &InteractiveBounds,
|
||||||
layout: &LayoutState,
|
layout: &LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
|
@ -2556,7 +2574,7 @@ impl EditorElement {
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
layout: &LayoutState,
|
layout: &LayoutState,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let interactive_bounds = InteractiveBounds {
|
let interactive_bounds = InteractiveBounds {
|
||||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||||
|
@ -2779,7 +2797,7 @@ impl LineWithInvisibles {
|
||||||
content_origin: gpui::Point<Pixels>,
|
content_origin: gpui::Point<Pixels>,
|
||||||
whitespace_setting: ShowWhitespaceSetting,
|
whitespace_setting: ShowWhitespaceSetting,
|
||||||
selection_ranges: &[Range<DisplayPoint>],
|
selection_ranges: &[Range<DisplayPoint>],
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let line_height = layout.position_map.line_height;
|
let line_height = layout.position_map.line_height;
|
||||||
let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
|
let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
|
||||||
|
@ -2813,7 +2831,7 @@ impl LineWithInvisibles {
|
||||||
row: u32,
|
row: u32,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
whitespace_setting: ShowWhitespaceSetting,
|
whitespace_setting: ShowWhitespaceSetting,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let allowed_invisibles_regions = match whitespace_setting {
|
let allowed_invisibles_regions = match whitespace_setting {
|
||||||
ShowWhitespaceSetting::None => return,
|
ShowWhitespaceSetting::None => return,
|
||||||
|
@ -2862,7 +2880,7 @@ impl Element for EditorElement {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_element_state: Option<Self::State>,
|
_element_state: Option<Self::State>,
|
||||||
cx: &mut gpui::WindowContext,
|
cx: &mut gpui::ElementContext,
|
||||||
) -> (gpui::LayoutId, Self::State) {
|
) -> (gpui::LayoutId, Self::State) {
|
||||||
cx.with_view_id(self.editor.entity_id(), |cx| {
|
cx.with_view_id(self.editor.entity_id(), |cx| {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
@ -2874,12 +2892,13 @@ impl Element for EditorElement {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
style.size.height = self.style.text.line_height_in_pixels(rem_size).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 } => {
|
EditorMode::AutoHeight { max_lines } => {
|
||||||
let editor_handle = cx.view().clone();
|
let editor_handle = cx.view().clone();
|
||||||
let max_line_number_width =
|
let max_line_number_width =
|
||||||
self.max_line_number_width(&editor.snapshot(cx), cx);
|
self.max_line_number_width(&editor.snapshot(cx), cx);
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
cx.request_measured_layout(
|
cx.request_measured_layout(
|
||||||
Style::default(),
|
Style::default(),
|
||||||
move |known_dimensions, _, cx| {
|
move |known_dimensions, _, cx| {
|
||||||
|
@ -2896,12 +2915,13 @@ impl Element for EditorElement {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
EditorMode::Full => {
|
EditorMode::Full => {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
style.size.height = 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,
|
&mut self,
|
||||||
bounds: Bounds<gpui::Pixels>,
|
bounds: Bounds<gpui::Pixels>,
|
||||||
_element_state: &mut Self::State,
|
_element_state: &mut Self::State,
|
||||||
cx: &mut gpui::WindowContext,
|
cx: &mut gpui::ElementContext,
|
||||||
) {
|
) {
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
|
|
||||||
|
@ -2943,9 +2963,10 @@ impl Element for EditorElement {
|
||||||
self.register_key_listeners(cx);
|
self.register_key_listeners(cx);
|
||||||
|
|
||||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||||
let input_handler =
|
cx.handle_input(
|
||||||
ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
&focus_handle,
|
||||||
cx.handle_input(&focus_handle, input_handler);
|
ElementInputHandler::new(bounds, self.editor.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
|
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
|
||||||
if layout.gutter_size.width > Pixels::ZERO {
|
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 {
|
let bounds = match self.shape {
|
||||||
CursorShape::Bar => Bounds {
|
CursorShape::Bar => Bounds {
|
||||||
origin: self.origin + origin,
|
origin: self.origin + origin,
|
||||||
|
@ -3275,7 +3296,7 @@ pub struct HighlightedRangeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HighlightedRange {
|
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 {
|
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(self.start_y, &self.lines[0..1], bounds, cx);
|
||||||
self.paint_lines(
|
self.paint_lines(
|
||||||
|
@ -3294,7 +3315,7 @@ impl HighlightedRange {
|
||||||
start_y: Pixels,
|
start_y: Pixels,
|
||||||
lines: &[HighlightedRangeLine],
|
lines: &[HighlightedRangeLine],
|
||||||
_bounds: Bounds<Pixels>,
|
_bounds: Bounds<Pixels>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -3512,6 +3533,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let state = cx
|
let state = cx
|
||||||
.update_window(window.into(), |view, cx| {
|
.update_window(window.into(), |view, cx| {
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
cx.with_view_id(view.entity_id(), |cx| {
|
cx.with_view_id(view.entity_id(), |cx| {
|
||||||
element.compute_layout(
|
element.compute_layout(
|
||||||
Bounds {
|
Bounds {
|
||||||
|
@ -3522,6 +3544,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(state.selections.len(), 1);
|
assert_eq!(state.selections.len(), 1);
|
||||||
|
@ -3606,6 +3629,7 @@ mod tests {
|
||||||
|
|
||||||
let state = cx
|
let state = cx
|
||||||
.update_window(window.into(), |view, cx| {
|
.update_window(window.into(), |view, cx| {
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
cx.with_view_id(view.entity_id(), |cx| {
|
cx.with_view_id(view.entity_id(), |cx| {
|
||||||
element.compute_layout(
|
element.compute_layout(
|
||||||
Bounds {
|
Bounds {
|
||||||
|
@ -3616,6 +3640,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(state.selections.len(), 1);
|
assert_eq!(state.selections.len(), 1);
|
||||||
let local_selections = &state.selections[0].1;
|
let local_selections = &state.selections[0].1;
|
||||||
|
@ -3670,6 +3695,7 @@ mod tests {
|
||||||
let mut element = EditorElement::new(&editor, style);
|
let mut element = EditorElement::new(&editor, style);
|
||||||
let state = cx
|
let state = cx
|
||||||
.update_window(window.into(), |view, cx| {
|
.update_window(window.into(), |view, cx| {
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
cx.with_view_id(view.entity_id(), |cx| {
|
cx.with_view_id(view.entity_id(), |cx| {
|
||||||
element.compute_layout(
|
element.compute_layout(
|
||||||
Bounds {
|
Bounds {
|
||||||
|
@ -3680,6 +3706,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let size = state.position_map.size;
|
let size = state.position_map.size;
|
||||||
|
|
||||||
|
@ -3695,7 +3722,9 @@ mod tests {
|
||||||
|
|
||||||
// Don't panic.
|
// Don't panic.
|
||||||
let bounds = Bounds::<Pixels>::new(Default::default(), size);
|
let bounds = Bounds::<Pixels>::new(Default::default(), size);
|
||||||
cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx))
|
cx.update_window(window.into(), |_, cx| {
|
||||||
|
cx.with_element_context(|cx| element.paint(bounds, &mut (), cx))
|
||||||
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3871,6 +3900,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let layout_state = cx
|
let layout_state = cx
|
||||||
.update_window(window.into(), |_, cx| {
|
.update_window(window.into(), |_, cx| {
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
element.compute_layout(
|
element.compute_layout(
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: point(px(500.), px(500.)),
|
origin: point(px(500.), px(500.)),
|
||||||
|
@ -3879,6 +3909,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
layout_state
|
layout_state
|
||||||
|
|
|
@ -247,7 +247,7 @@ fn show_hover(
|
||||||
};
|
};
|
||||||
|
|
||||||
// query the LSP for hover info
|
// query the LSP for hover info
|
||||||
let hover_request = cx.update(|_, cx| {
|
let hover_request = cx.update(|cx| {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.hover(&buffer, buffer_position, 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 :(
|
// This check is required because index.get_path() unwraps internally :(
|
||||||
check_path_to_repo_path_errors(relative_file_path)?;
|
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,
|
Some(entry) => entry.id,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,7 @@ impl GitRepository for LibGitRepository {
|
||||||
Ok(Some(String::from_utf8(content)?))
|
Ok(Some(String::from_utf8(content)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
match logic(&self, relative_file_path) {
|
match logic(self, relative_file_path) {
|
||||||
Ok(value) => return value,
|
Ok(value) => return value,
|
||||||
Err(err) => log::error!("Error loading head text: {:?}", err),
|
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 {
|
fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool {
|
||||||
if let Some(index) = repo.index().log_err() {
|
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 let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() {
|
||||||
if entry.mtime.seconds() == mtime.as_secs() as i32
|
if entry.mtime.seconds() == mtime.as_secs() as i32
|
||||||
&& entry.mtime.nanoseconds() == mtime.subsec_nanos()
|
&& entry.mtime.nanoseconds() == mtime.subsec_nanos()
|
||||||
|
|
|
@ -165,7 +165,7 @@ impl BufferDiff {
|
||||||
let mut tree = SumTree::new();
|
let mut tree = SumTree::new();
|
||||||
|
|
||||||
let buffer_text = buffer.as_rope().to_string();
|
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 {
|
if let Some(patch) = patch {
|
||||||
let mut divergence = 0;
|
let mut divergence = 0;
|
||||||
|
|
|
@ -40,14 +40,25 @@ use std::any::{Any, TypeId};
|
||||||
/// register_action!(Paste);
|
/// register_action!(Paste);
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Action: 'static {
|
pub trait Action: 'static {
|
||||||
|
/// Clone the action into a new box
|
||||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||||
|
|
||||||
|
/// Cast the action to the any type
|
||||||
fn as_any(&self) -> &dyn Any;
|
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;
|
fn partial_eq(&self, action: &dyn Action) -> bool;
|
||||||
|
|
||||||
|
/// Get the name of this action, for displaying in UI
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
|
/// Get the name of this action for debugging
|
||||||
fn debug_name() -> &'static str
|
fn debug_name() -> &'static str
|
||||||
where
|
where
|
||||||
Self: Sized;
|
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>>
|
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
@ -62,6 +73,7 @@ impl std::fmt::Debug for dyn Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn Action {
|
impl dyn Action {
|
||||||
|
/// Get the type id of this action
|
||||||
pub fn type_id(&self) -> TypeId {
|
pub fn type_id(&self) -> TypeId {
|
||||||
self.as_any().type_id()
|
self.as_any().type_id()
|
||||||
}
|
}
|
||||||
|
@ -170,6 +182,7 @@ impl ActionRegistry {
|
||||||
macro_rules! actions {
|
macro_rules! actions {
|
||||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
($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)]
|
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)]
|
||||||
#[serde(crate = "gpui::private::serde")]
|
#[serde(crate = "gpui::private::serde")]
|
||||||
pub struct $name;
|
pub struct $name;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
mod async_context;
|
mod async_context;
|
||||||
mod entity_map;
|
mod entity_map;
|
||||||
mod model_context;
|
mod model_context;
|
||||||
|
|
|
@ -213,7 +213,12 @@ impl AsyncWindowContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenience method for [WindowContext::update()]
|
/// 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,
|
&mut self,
|
||||||
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
||||||
) -> Result<R> {
|
) -> Result<R> {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||||
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
||||||
|
@ -352,7 +350,7 @@ impl TestAppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `TestWindow` backing the given handle.
|
/// 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
|
self.app
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.windows
|
.windows
|
||||||
|
@ -642,8 +640,11 @@ impl<'a> VisualTestContext {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Can't draw to this window without a root view")
|
.expect("Can't draw to this window without a root view")
|
||||||
.entity_id();
|
.entity_id();
|
||||||
|
|
||||||
|
cx.with_element_context(|cx| {
|
||||||
cx.with_view_id(entity_id, |cx| {
|
cx.with_view_id(entity_id, |cx| {
|
||||||
f(cx).draw(origin, space, cx);
|
f(cx).draw(origin, space, cx);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
|
|
@ -8,8 +8,12 @@ use std::{
|
||||||
sync::atomic::{AtomicUsize, Ordering::SeqCst},
|
sync::atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A source of assets for this app to use.
|
||||||
pub trait AssetSource: 'static + Send + Sync {
|
pub trait AssetSource: 'static + Send + Sync {
|
||||||
|
/// Load the given asset from the source path.
|
||||||
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
||||||
|
|
||||||
|
/// List the assets at the given path.
|
||||||
fn list(&self, path: &str) -> Result<Vec<SharedString>>;
|
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)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct ImageId(usize);
|
pub struct ImageId(usize);
|
||||||
|
|
||||||
|
/// A cached and processed image.
|
||||||
pub struct ImageData {
|
pub struct ImageData {
|
||||||
|
/// The ID associated with this image
|
||||||
pub id: ImageId,
|
pub id: ImageId,
|
||||||
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
|
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageData {
|
impl ImageData {
|
||||||
|
/// Create a new image from the given data.
|
||||||
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
|
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
|
||||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
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] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the size of this image, in pixels
|
||||||
pub fn size(&self) -> Size<DevicePixels> {
|
pub fn size(&self) -> Size<DevicePixels> {
|
||||||
let (width, height) = self.data.dimensions();
|
let (width, height) = self.data.dimensions();
|
||||||
size(width.into(), height.into())
|
size(width.into(), height.into())
|
||||||
|
|
|
@ -2,6 +2,7 @@ use anyhow::bail;
|
||||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Convert an RGB hex color code number to a color type
|
||||||
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
||||||
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||||
let g = ((hex >> 8) & 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()
|
Rgba { r, g, b, a: 1.0 }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert an RGBA hex color code number to [`Rgba`]
|
||||||
pub fn rgba(hex: u32) -> Rgba {
|
pub fn rgba(hex: u32) -> Rgba {
|
||||||
let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
|
let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
|
||||||
let g = ((hex >> 16) & 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 }
|
Rgba { r, g, b, a }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An RGBA color
|
||||||
#[derive(PartialEq, Clone, Copy, Default)]
|
#[derive(PartialEq, Clone, Copy, Default)]
|
||||||
pub struct Rgba {
|
pub struct Rgba {
|
||||||
|
/// The red component of the color, in the range 0.0 to 1.0
|
||||||
pub r: f32,
|
pub r: f32,
|
||||||
|
/// The green component of the color, in the range 0.0 to 1.0
|
||||||
pub g: f32,
|
pub g: f32,
|
||||||
|
/// The blue component of the color, in the range 0.0 to 1.0
|
||||||
pub b: f32,
|
pub b: f32,
|
||||||
|
/// The alpha component of the color, in the range 0.0 to 1.0
|
||||||
pub a: f32,
|
pub a: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +39,8 @@ impl fmt::Debug for Rgba {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn blend(&self, other: Rgba) -> Self {
|
||||||
if other.a >= 1.0 {
|
if other.a >= 1.0 {
|
||||||
other
|
other
|
||||||
|
@ -165,12 +174,20 @@ impl TryFrom<&'_ str> for Rgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An HSLA color
|
||||||
#[derive(Default, Copy, Clone, Debug)]
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Hsla {
|
pub struct Hsla {
|
||||||
|
/// Hue, in a range from 0 to 1
|
||||||
pub h: f32,
|
pub h: f32,
|
||||||
|
|
||||||
|
/// Saturation, in a range from 0 to 1
|
||||||
pub s: f32,
|
pub s: f32,
|
||||||
|
|
||||||
|
/// Lightness, in a range from 0 to 1
|
||||||
pub l: f32,
|
pub l: f32,
|
||||||
|
|
||||||
|
/// Alpha, in a range from 0 to 1
|
||||||
pub a: f32,
|
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 {}
|
impl Eq for Hsla {}
|
||||||
|
|
||||||
|
/// Construct an [`Hsla`] object from plain values
|
||||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: h.clamp(0., 1.),
|
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 {
|
pub fn black() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.,
|
h: 0.,
|
||||||
|
@ -253,6 +242,7 @@ pub fn black() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transparent black in [`Hsla`]
|
||||||
pub fn transparent_black() -> Hsla {
|
pub fn transparent_black() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.,
|
h: 0.,
|
||||||
|
@ -262,6 +252,7 @@ pub fn transparent_black() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pure white in [`Hsla`]
|
||||||
pub fn white() -> Hsla {
|
pub fn white() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.,
|
h: 0.,
|
||||||
|
@ -271,6 +262,7 @@ pub fn white() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color red in [`Hsla`]
|
||||||
pub fn red() -> Hsla {
|
pub fn red() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.,
|
h: 0.,
|
||||||
|
@ -280,6 +272,7 @@ pub fn red() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color blue in [`Hsla`]
|
||||||
pub fn blue() -> Hsla {
|
pub fn blue() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.6,
|
h: 0.6,
|
||||||
|
@ -289,6 +282,7 @@ pub fn blue() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color green in [`Hsla`]
|
||||||
pub fn green() -> Hsla {
|
pub fn green() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.33,
|
h: 0.33,
|
||||||
|
@ -298,6 +292,7 @@ pub fn green() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color yellow in [`Hsla`]
|
||||||
pub fn yellow() -> Hsla {
|
pub fn yellow() -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: 0.16,
|
h: 0.16,
|
||||||
|
@ -308,6 +303,41 @@ pub fn yellow() -> Hsla {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.
|
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||||
pub fn is_transparent(&self) -> bool {
|
pub fn is_transparent(&self) -> bool {
|
||||||
self.a == 0.0
|
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 {
|
pub fn grayscale(&self) -> Self {
|
||||||
Hsla {
|
Hsla {
|
||||||
h: self.h,
|
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::{
|
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,
|
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
pub(crate) use smallvec::SmallVec;
|
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.
|
/// 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.
|
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||||
/// 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, see the module-level documentation
|
||||||
/// You can create custom elements by implementing this trait.
|
/// for more details.
|
||||||
pub trait Element: 'static + IntoElement {
|
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;
|
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(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State);
|
) -> (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 {
|
fn into_any(self) -> AnyElement {
|
||||||
AnyElement::new(self)
|
AnyElement::new(self)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +72,7 @@ pub trait Element: 'static + IntoElement {
|
||||||
/// Implemented by any type that can be converted into an element.
|
/// Implemented by any type that can be converted into an element.
|
||||||
pub trait IntoElement: Sized {
|
pub trait IntoElement: Sized {
|
||||||
/// The specific type of element into which the implementing type is converted.
|
/// 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;
|
type Element: Element;
|
||||||
|
|
||||||
/// The [`ElementId`] of self once converted into an [`Element`].
|
/// The [`ElementId`] of self once converted into an [`Element`].
|
||||||
|
@ -51,8 +95,8 @@ pub trait IntoElement: Sized {
|
||||||
self,
|
self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<T>,
|
available_space: Size<T>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
|
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
|
||||||
) -> R
|
) -> R
|
||||||
where
|
where
|
||||||
T: Clone + Default + Debug + Into<AvailableSpace>,
|
T: Clone + Default + Debug + Into<AvailableSpace>,
|
||||||
|
@ -81,7 +125,10 @@ pub trait IntoElement: Sized {
|
||||||
|
|
||||||
impl<T: IntoElement> FluentBuilder for T {}
|
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 {
|
pub trait Render: 'static + Sized {
|
||||||
|
/// Render this view into an element tree.
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
|
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.
|
/// 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 {
|
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;
|
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 {
|
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
|
fn child(mut self, child: impl IntoElement) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.children_mut().push(child.into_element().into_any());
|
self.extend(std::iter::once(child.into_element().into_any()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add multiple child elements to this element.
|
||||||
fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
|
fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.children_mut()
|
self.extend(children.into_iter().map(|child| child.into_any_element()));
|
||||||
.extend(children.into_iter().map(|child| child.into_any_element()));
|
|
||||||
self
|
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>);
|
pub struct Component<C: RenderOnce>(Option<C>);
|
||||||
|
|
||||||
impl<C: RenderOnce> Component<C> {
|
impl<C: RenderOnce> Component<C> {
|
||||||
|
/// Create a new component from the given RenderOnce type.
|
||||||
pub fn new(component: C) -> Self {
|
pub fn new(component: C) -> Self {
|
||||||
Component(Some(component))
|
Component(Some(component))
|
||||||
}
|
}
|
||||||
|
@ -132,14 +193,19 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (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);
|
let layout_id = element.request_layout(cx);
|
||||||
(layout_id, element)
|
(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)
|
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)]
|
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||||
|
|
||||||
trait ElementObject {
|
trait ElementObject {
|
||||||
fn element_id(&self) -> Option<ElementId>;
|
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(
|
fn measure(
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels>;
|
) -> Size<Pixels>;
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
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>,
|
element: Option<E>,
|
||||||
phase: ElementDrawPhase<E::State>,
|
phase: ElementDrawPhase<E::State>,
|
||||||
}
|
}
|
||||||
|
@ -213,7 +281,7 @@ impl<E: Element> DrawableElement<E> {
|
||||||
self.element.as_ref()?.element_id()
|
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, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
|
||||||
{
|
{
|
||||||
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
||||||
|
@ -235,7 +303,7 @@ impl<E: Element> DrawableElement<E> {
|
||||||
layout_id
|
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 {
|
match self.phase {
|
||||||
ElementDrawPhase::LayoutRequested {
|
ElementDrawPhase::LayoutRequested {
|
||||||
layout_id,
|
layout_id,
|
||||||
|
@ -280,7 +348,7 @@ impl<E: Element> DrawableElement<E> {
|
||||||
fn measure(
|
fn measure(
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels> {
|
) -> Size<Pixels> {
|
||||||
if matches!(&self.phase, ElementDrawPhase::Start) {
|
if matches!(&self.phase, ElementDrawPhase::Start) {
|
||||||
self.request_layout(cx);
|
self.request_layout(cx);
|
||||||
|
@ -321,7 +389,7 @@ impl<E: Element> DrawableElement<E> {
|
||||||
mut self,
|
mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Option<E::State> {
|
) -> Option<E::State> {
|
||||||
self.measure(available_space, cx);
|
self.measure(available_space, cx);
|
||||||
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
||||||
|
@ -337,18 +405,18 @@ where
|
||||||
self.as_ref().unwrap().element_id()
|
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)
|
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);
|
DrawableElement::paint(self.take().unwrap(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels> {
|
) -> Size<Pixels> {
|
||||||
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
|
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
|
||||||
}
|
}
|
||||||
|
@ -357,16 +425,17 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
|
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>);
|
pub struct AnyElement(ArenaBox<dyn ElementObject>);
|
||||||
|
|
||||||
impl AnyElement {
|
impl AnyElement {
|
||||||
pub fn new<E>(element: E) -> Self
|
pub(crate) fn new<E>(element: E) -> Self
|
||||||
where
|
where
|
||||||
E: 'static + Element,
|
E: 'static + Element,
|
||||||
E::State: Any,
|
E::State: Any,
|
||||||
|
@ -377,11 +446,14 @@ impl AnyElement {
|
||||||
AnyElement(element)
|
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)
|
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)
|
self.0.paint(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +461,7 @@ impl AnyElement {
|
||||||
pub fn measure(
|
pub fn measure(
|
||||||
&mut self,
|
&mut self,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Size<Pixels> {
|
) -> Size<Pixels> {
|
||||||
self.0.measure(available_space, cx)
|
self.0.measure(available_space, cx)
|
||||||
}
|
}
|
||||||
|
@ -399,11 +471,12 @@ impl AnyElement {
|
||||||
&mut self,
|
&mut self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
self.0.draw(origin, available_space, cx)
|
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> {
|
pub fn inner_id(&self) -> Option<ElementId> {
|
||||||
self.0.element_id()
|
self.0.element_id()
|
||||||
}
|
}
|
||||||
|
@ -415,13 +488,13 @@ impl Element for AnyElement {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
let layout_id = self.request_layout(cx);
|
let layout_id = self.request_layout(cx);
|
||||||
(layout_id, ())
|
(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)
|
self.paint(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,7 +536,7 @@ impl Element for () {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: Option<Self::State>,
|
_state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
(cx.request_layout(&crate::Style::default(), None), ())
|
(cx.request_layout(&crate::Style::default(), None), ())
|
||||||
}
|
}
|
||||||
|
@ -472,7 +545,7 @@ impl Element for () {
|
||||||
&mut self,
|
&mut self,
|
||||||
_bounds: Bounds<Pixels>,
|
_bounds: Bounds<Pixels>,
|
||||||
_state: &mut Self::State,
|
_state: &mut Self::State,
|
||||||
_cx: &mut WindowContext,
|
_cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use refineable::Refineable as _;
|
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 {
|
Canvas {
|
||||||
paint_callback: Some(Box::new(callback)),
|
paint_callback: Some(Box::new(callback)),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||||
|
/// custom element
|
||||||
pub struct Canvas {
|
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,
|
style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +36,7 @@ impl Element for Canvas {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (crate::LayoutId, Self::State) {
|
) -> (crate::LayoutId, Self::State) {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.refine(&self.style);
|
style.refine(&self.style);
|
||||||
|
@ -40,7 +44,7 @@ impl Element for Canvas {
|
||||||
(layout_id, style)
|
(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| {
|
style.paint(bounds, cx, |cx| {
|
||||||
(self.paint_callback.take().unwrap())(&bounds, 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 std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
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,
|
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
|
||||||
StyleRefinement, Styled, WindowContext,
|
StyleRefinement, Styled,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use media::core_video::CVImageBuffer;
|
use media::core_video::CVImageBuffer;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
/// A source of image content.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ImageSource {
|
pub enum ImageSource {
|
||||||
/// Image content will be loaded from provided URI at render time.
|
/// Image content will be loaded from provided URI at render time.
|
||||||
Uri(SharedUrl),
|
Uri(SharedUrl),
|
||||||
|
/// Cached image data
|
||||||
Data(Arc<ImageData>),
|
Data(Arc<ImageData>),
|
||||||
|
// TODO: move surface definitions into mac platform module
|
||||||
|
/// A CoreVideo image buffer
|
||||||
Surface(CVImageBuffer),
|
Surface(CVImageBuffer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,12 +51,14 @@ impl From<CVImageBuffer> for ImageSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An image element.
|
||||||
pub struct Img {
|
pub struct Img {
|
||||||
interactivity: Interactivity,
|
interactivity: Interactivity,
|
||||||
source: ImageSource,
|
source: ImageSource,
|
||||||
grayscale: bool,
|
grayscale: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new image element.
|
||||||
pub fn img(source: impl Into<ImageSource>) -> Img {
|
pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||||
Img {
|
Img {
|
||||||
interactivity: Interactivity::default(),
|
interactivity: Interactivity::default(),
|
||||||
|
@ -62,6 +68,7 @@ pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Img {
|
impl Img {
|
||||||
|
/// Set the image to be displayed in grayscale.
|
||||||
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
pub fn grayscale(mut self, grayscale: bool) -> Self {
|
||||||
self.grayscale = grayscale;
|
self.grayscale = grayscale;
|
||||||
self
|
self
|
||||||
|
@ -74,7 +81,7 @@ impl Element for Img {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
element_state: Option<Self::State>,
|
element_state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
self.interactivity
|
self.interactivity
|
||||||
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
||||||
|
@ -84,7 +91,7 @@ impl Element for Img {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
element_state: &mut Self::State,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let source = self.source.clone();
|
let source = self.source.clone();
|
||||||
self.interactivity.paint(
|
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::{
|
use crate::{
|
||||||
point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
point, px, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Element,
|
||||||
DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
|
||||||
StyleRefinement, Styled, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
use refineable::Refineable as _;
|
use refineable::Refineable as _;
|
||||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
use sum_tree::{Bias, SumTree};
|
use sum_tree::{Bias, SumTree};
|
||||||
|
|
||||||
|
/// Construct a new list element
|
||||||
pub fn list(state: ListState) -> List {
|
pub fn list(state: ListState) -> List {
|
||||||
List {
|
List {
|
||||||
state,
|
state,
|
||||||
|
@ -15,11 +24,13 @@ pub fn list(state: ListState) -> List {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A list element
|
||||||
pub struct List {
|
pub struct List {
|
||||||
state: ListState,
|
state: ListState,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The list state that views must hold on behalf of the list element.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ListState(Rc<RefCell<StateInner>>);
|
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||||
|
|
||||||
|
@ -35,15 +46,24 @@ struct StateInner {
|
||||||
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum ListAlignment {
|
pub enum ListAlignment {
|
||||||
|
/// The list is scrolling from top to bottom, like most lists.
|
||||||
Top,
|
Top,
|
||||||
|
/// The list is scrolling from bottom to top, like a chat log.
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A scroll event that has been converted to be in terms of the list's items.
|
||||||
pub struct ListScrollEvent {
|
pub struct ListScrollEvent {
|
||||||
|
/// The range of items currently visible in the list, after applying the scroll event.
|
||||||
pub visible_range: Range<usize>,
|
pub visible_range: Range<usize>,
|
||||||
|
|
||||||
|
/// The number of items that are currently visible in the list, after applying the scroll event.
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
|
|
||||||
|
/// Whether the list has been scrolled.
|
||||||
pub is_scrolled: bool,
|
pub is_scrolled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +94,11 @@ struct UnrenderedCount(usize);
|
||||||
struct Height(Pixels);
|
struct Height(Pixels);
|
||||||
|
|
||||||
impl ListState {
|
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>(
|
pub fn new<F>(
|
||||||
element_count: usize,
|
element_count: usize,
|
||||||
orientation: ListAlignment,
|
orientation: ListAlignment,
|
||||||
|
@ -111,10 +136,13 @@ impl ListState {
|
||||||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of items in this list.
|
||||||
pub fn item_count(&self) -> usize {
|
pub fn item_count(&self) -> usize {
|
||||||
self.0.borrow().items.summary().count
|
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) {
|
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||||
let state = &mut *self.0.borrow_mut();
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
|
||||||
|
@ -141,6 +169,7 @@ impl ListState {
|
||||||
state.items = new_heights;
|
state.items = new_heights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a handler that will be called when the list is scrolled.
|
||||||
pub fn set_scroll_handler(
|
pub fn set_scroll_handler(
|
||||||
&self,
|
&self,
|
||||||
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||||
|
@ -148,10 +177,12 @@ impl ListState {
|
||||||
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
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 {
|
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||||
self.0.borrow().logical_scroll_top()
|
self.0.borrow().logical_scroll_top()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll the list to the given offset
|
||||||
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||||
let state = &mut *self.0.borrow_mut();
|
let state = &mut *self.0.borrow_mut();
|
||||||
let item_count = state.items.summary().count;
|
let item_count = state.items.summary().count;
|
||||||
|
@ -163,6 +194,7 @@ impl ListState {
|
||||||
state.logical_scroll_top = Some(scroll_top);
|
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) {
|
pub fn scroll_to_reveal_item(&self, ix: usize) {
|
||||||
let state = &mut *self.0.borrow_mut();
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
|
||||||
|
@ -193,7 +225,8 @@ impl ListState {
|
||||||
state.logical_scroll_top = Some(scroll_top);
|
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>> {
|
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
|
||||||
let state = &*self.0.borrow();
|
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)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ListOffset {
|
pub struct ListOffset {
|
||||||
|
/// The index of an item in the list
|
||||||
pub item_ix: usize,
|
pub item_ix: usize,
|
||||||
|
/// The number of pixels to offset from the item index.
|
||||||
pub offset_in_item: Pixels,
|
pub offset_in_item: Pixels,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +359,7 @@ impl Element for List {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: Option<Self::State>,
|
_state: Option<Self::State>,
|
||||||
cx: &mut crate::WindowContext,
|
cx: &mut crate::ElementContext,
|
||||||
) -> (crate::LayoutId, Self::State) {
|
) -> (crate::LayoutId, Self::State) {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.refine(&self.style);
|
style.refine(&self.style);
|
||||||
|
@ -336,7 +373,7 @@ impl Element for List {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<crate::Pixels>,
|
bounds: Bounds<crate::Pixels>,
|
||||||
_state: &mut Self::State,
|
_state: &mut Self::State,
|
||||||
cx: &mut crate::WindowContext,
|
cx: &mut crate::ElementContext,
|
||||||
) {
|
) {
|
||||||
let state = &mut *self.state.0.borrow_mut();
|
let state = &mut *self.state.0.borrow_mut();
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,17 @@ use smallvec::SmallVec;
|
||||||
use taffy::style::{Display, Position};
|
use taffy::style::{Display, Position};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
|
point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement,
|
||||||
Point, Size, Style, WindowContext,
|
Pixels, Point, Size, Style,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The state that the overlay element uses to track its children.
|
||||||
pub struct OverlayState {
|
pub struct OverlayState {
|
||||||
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
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 {
|
pub struct Overlay {
|
||||||
children: SmallVec<[AnyElement; 2]>,
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
anchor_corner: AnchorCorner,
|
anchor_corner: AnchorCorner,
|
||||||
|
@ -60,8 +63,8 @@ impl Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for Overlay {
|
impl ParentElement for Overlay {
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||||
&mut self.children
|
self.children.extend(elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ impl Element for Overlay {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (crate::LayoutId, Self::State) {
|
) -> (crate::LayoutId, Self::State) {
|
||||||
let child_layout_ids = self
|
let child_layout_ids = self
|
||||||
.children
|
.children
|
||||||
|
@ -94,7 +97,7 @@ impl Element for Overlay {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: crate::Bounds<crate::Pixels>,
|
bounds: crate::Bounds<crate::Pixels>,
|
||||||
element_state: &mut Self::State,
|
element_state: &mut Self::State,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
if element_state.child_layout_ids.is_empty() {
|
if element_state.child_layout_ids.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -191,15 +194,21 @@ enum Axis {
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which algorithm to use when fitting the overlay to be inside the window.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum OverlayFitMode {
|
pub enum OverlayFitMode {
|
||||||
|
/// Snap the overlay to the window edge
|
||||||
SnapToWindow,
|
SnapToWindow,
|
||||||
|
/// Switch which corner anchor this overlay is attached to
|
||||||
SwitchAnchor,
|
SwitchAnchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which algorithm to use when positioning the overlay.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum OverlayPositionMode {
|
pub enum OverlayPositionMode {
|
||||||
|
/// Position the overlay relative to the window
|
||||||
Window,
|
Window,
|
||||||
|
/// Position the overlay relative to its parent
|
||||||
Local,
|
Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,11 +235,16 @@ impl OverlayPositionMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which corner of the overlay should be considered the anchor.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AnchorCorner {
|
pub enum AnchorCorner {
|
||||||
|
/// The top left corner
|
||||||
TopLeft,
|
TopLeft,
|
||||||
|
/// The top right corner
|
||||||
TopRight,
|
TopRight,
|
||||||
|
/// The bottom left corner
|
||||||
BottomLeft,
|
BottomLeft,
|
||||||
|
/// The bottom right corner
|
||||||
BottomRight,
|
BottomRight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +269,7 @@ impl AnchorCorner {
|
||||||
Bounds { origin, size }
|
Bounds { origin, size }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the point corresponding to this anchor corner in `bounds`.
|
||||||
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||||
match self {
|
match self {
|
||||||
Self::TopLeft => bounds.origin,
|
Self::TopLeft => bounds.origin,
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
|
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
|
||||||
IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
|
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
/// An SVG element.
|
||||||
pub struct Svg {
|
pub struct Svg {
|
||||||
interactivity: Interactivity,
|
interactivity: Interactivity,
|
||||||
path: Option<SharedString>,
|
path: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new SVG element.
|
||||||
pub fn svg() -> Svg {
|
pub fn svg() -> Svg {
|
||||||
Svg {
|
Svg {
|
||||||
interactivity: Interactivity::default(),
|
interactivity: Interactivity::default(),
|
||||||
|
@ -17,6 +19,7 @@ pub fn svg() -> Svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Svg {
|
impl Svg {
|
||||||
|
/// Set the path to the SVG file for this element.
|
||||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||||
self.path = Some(path.into());
|
self.path = Some(path.into());
|
||||||
self
|
self
|
||||||
|
@ -29,7 +32,7 @@ impl Element for Svg {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
element_state: Option<Self::State>,
|
element_state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
self.interactivity.layout(element_state, cx, |style, cx| {
|
self.interactivity.layout(element_state, cx, |style, cx| {
|
||||||
cx.request_layout(&style, None)
|
cx.request_layout(&style, None)
|
||||||
|
@ -40,7 +43,7 @@ impl Element for Svg {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
element_state: &mut Self::State,
|
element_state: &mut Self::State,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) where
|
) where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
|
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
||||||
MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
|
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||||
WhiteSpace, WindowContext, WrappedLine,
|
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||||
|
TOOLTIP_DELAY,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
use smallvec::SmallVec;
|
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;
|
use util::ResultExt;
|
||||||
|
|
||||||
impl Element for &'static str {
|
impl Element for &'static str {
|
||||||
|
@ -15,14 +22,14 @@ impl Element for &'static str {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||||
(layout_id, state)
|
(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)
|
state.paint(bounds, self, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,14 +52,14 @@ impl Element for SharedString {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(self.clone(), None, cx);
|
let layout_id = state.layout(self.clone(), None, cx);
|
||||||
(layout_id, state)
|
(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();
|
let text_str: &str = self.as_ref();
|
||||||
state.paint(bounds, text_str, cx)
|
state.paint(bounds, text_str, cx)
|
||||||
}
|
}
|
||||||
|
@ -81,6 +88,7 @@ pub struct StyledText {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledText {
|
impl StyledText {
|
||||||
|
/// Construct a new styled text element from the given string.
|
||||||
pub fn new(text: impl Into<SharedString>) -> Self {
|
pub fn new(text: impl Into<SharedString>) -> Self {
|
||||||
StyledText {
|
StyledText {
|
||||||
text: text.into(),
|
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(
|
pub fn with_highlights(
|
||||||
mut self,
|
mut self,
|
||||||
default_style: &TextStyle,
|
default_style: &TextStyle,
|
||||||
|
@ -121,14 +131,14 @@ impl Element for StyledText {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: Option<Self::State>,
|
_: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
let mut state = TextState::default();
|
let mut state = TextState::default();
|
||||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||||
(layout_id, state)
|
(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)
|
state.paint(bounds, &self.text, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +155,7 @@ impl IntoElement for StyledText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
|
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
|
||||||
|
|
||||||
|
@ -164,7 +175,7 @@ impl TextState {
|
||||||
&mut self,
|
&mut self,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
runs: Option<Vec<TextRun>>,
|
runs: Option<Vec<TextRun>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> LayoutId {
|
) -> LayoutId {
|
||||||
let text_style = cx.text_style();
|
let text_style = cx.text_style();
|
||||||
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
||||||
|
@ -239,7 +250,7 @@ impl TextState {
|
||||||
layout_id
|
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 = self.lock();
|
||||||
let element_state = element_state
|
let element_state = element_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -284,11 +295,14 @@ impl TextState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text element that can be interacted with.
|
||||||
pub struct InteractiveText {
|
pub struct InteractiveText {
|
||||||
element_id: ElementId,
|
element_id: ElementId,
|
||||||
text: StyledText,
|
text: StyledText,
|
||||||
click_listener:
|
click_listener:
|
||||||
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
|
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>>,
|
clickable_ranges: Vec<Range<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,21 +311,30 @@ struct InteractiveTextClickEvent {
|
||||||
mouse_up_index: usize,
|
mouse_up_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub struct InteractiveTextState {
|
pub struct InteractiveTextState {
|
||||||
text_state: TextState,
|
text_state: TextState,
|
||||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
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 {
|
impl InteractiveText {
|
||||||
|
/// Creates a new InteractiveText from the given text.
|
||||||
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
|
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
|
||||||
Self {
|
Self {
|
||||||
element_id: id.into(),
|
element_id: id.into(),
|
||||||
text,
|
text,
|
||||||
click_listener: None,
|
click_listener: None,
|
||||||
|
hover_listener: None,
|
||||||
|
tooltip_builder: None,
|
||||||
clickable_ranges: Vec::new(),
|
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(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
ranges: Vec<Range<usize>>,
|
ranges: Vec<Range<usize>>,
|
||||||
|
@ -328,6 +351,25 @@ impl InteractiveText {
|
||||||
self.clickable_ranges = ranges;
|
self.clickable_ranges = ranges;
|
||||||
self
|
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 {
|
impl Element for InteractiveText {
|
||||||
|
@ -336,16 +378,21 @@ impl Element for InteractiveText {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
if let Some(InteractiveTextState {
|
if let Some(InteractiveTextState {
|
||||||
mouse_down_index, ..
|
mouse_down_index,
|
||||||
|
hovered_index,
|
||||||
|
active_tooltip,
|
||||||
|
..
|
||||||
}) = state
|
}) = state
|
||||||
{
|
{
|
||||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
||||||
let element_state = InteractiveTextState {
|
let element_state = InteractiveTextState {
|
||||||
text_state,
|
text_state,
|
||||||
mouse_down_index,
|
mouse_down_index,
|
||||||
|
hovered_index,
|
||||||
|
active_tooltip,
|
||||||
};
|
};
|
||||||
(layout_id, element_state)
|
(layout_id, element_state)
|
||||||
} else {
|
} else {
|
||||||
|
@ -353,12 +400,14 @@ impl Element for InteractiveText {
|
||||||
let element_state = InteractiveTextState {
|
let element_state = InteractiveTextState {
|
||||||
text_state,
|
text_state,
|
||||||
mouse_down_index: Rc::default(),
|
mouse_down_index: Rc::default(),
|
||||||
|
hovered_index: Rc::default(),
|
||||||
|
active_tooltip: Rc::default(),
|
||||||
};
|
};
|
||||||
(layout_id, element_state)
|
(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() {
|
if let Some(click_listener) = self.click_listener.take() {
|
||||||
let mouse_position = cx.mouse_position();
|
let mouse_position = cx.mouse_position();
|
||||||
if let Some(ix) = state.text_state.index_for_position(bounds, 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)
|
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::{
|
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,
|
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
||||||
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
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 {
|
pub struct UniformList {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
item_count: usize,
|
item_count: usize,
|
||||||
|
@ -63,18 +70,22 @@ pub struct UniformList {
|
||||||
scroll_handle: Option<UniformListScrollHandle>,
|
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)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UniformListScrollHandle {
|
pub struct UniformListScrollHandle {
|
||||||
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
|
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UniformListScrollHandle {
|
impl UniformListScrollHandle {
|
||||||
|
/// Create a new scroll handle to bind to a uniform list.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
deferred_scroll_to_item: Rc::new(RefCell::new(None)),
|
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) {
|
pub fn scroll_to_item(&mut self, ix: usize) {
|
||||||
self.deferred_scroll_to_item.replace(Some(ix));
|
self.deferred_scroll_to_item.replace(Some(ix));
|
||||||
}
|
}
|
||||||
|
@ -86,6 +97,7 @@ impl Styled for UniformList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UniformListState {
|
pub struct UniformListState {
|
||||||
interactive: InteractiveElementState,
|
interactive: InteractiveElementState,
|
||||||
|
@ -98,7 +110,7 @@ impl Element for UniformList {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
let max_items = self.item_count;
|
let max_items = self.item_count;
|
||||||
let item_size = state
|
let item_size = state
|
||||||
|
@ -146,7 +158,7 @@ impl Element for UniformList {
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Bounds<crate::Pixels>,
|
bounds: Bounds<crate::Pixels>,
|
||||||
element_state: &mut Self::State,
|
element_state: &mut Self::State,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
let style =
|
let style =
|
||||||
self.interactivity
|
self.interactivity
|
||||||
|
@ -262,12 +274,13 @@ impl IntoElement for UniformList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UniformList {
|
impl UniformList {
|
||||||
|
/// Selects a specific list item for measurement.
|
||||||
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
||||||
self.item_to_measure_index = item_index.unwrap_or(0);
|
self.item_to_measure_index = item_index.unwrap_or(0);
|
||||||
self
|
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 {
|
if self.item_count == 0 {
|
||||||
return Size::default();
|
return Size::default();
|
||||||
}
|
}
|
||||||
|
@ -284,6 +297,7 @@ impl UniformList {
|
||||||
item_to_measure.measure(available_space, cx)
|
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 {
|
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
||||||
self.scroll_handle = Some(handle);
|
self.scroll_handle = Some(handle);
|
||||||
self
|
self
|
||||||
|
|
|
@ -21,11 +21,15 @@ use waker_fn::waker_fn;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
|
|
||||||
|
/// A pointer to the executor that is currently running,
|
||||||
|
/// for spawning background tasks.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BackgroundExecutor {
|
pub struct BackgroundExecutor {
|
||||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A pointer to the executor that is currently running,
|
||||||
|
/// for spawning tasks on the main thread.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ForegroundExecutor {
|
pub struct ForegroundExecutor {
|
||||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||||
|
@ -37,11 +41,14 @@ pub struct ForegroundExecutor {
|
||||||
/// It implements [`Future`] so you can `.await` on it.
|
/// It implements [`Future`] so you can `.await` on it.
|
||||||
///
|
///
|
||||||
/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
|
/// 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]
|
#[must_use]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Task<T> {
|
pub enum Task<T> {
|
||||||
|
/// A task that is ready to return a value
|
||||||
Ready(Option<T>),
|
Ready(Option<T>),
|
||||||
|
|
||||||
|
/// A task that is currently running.
|
||||||
Spawned(async_task::Task<T>),
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct TaskLabel(NonZeroUsize);
|
pub struct TaskLabel(NonZeroUsize);
|
||||||
|
|
||||||
|
@ -97,6 +106,7 @@ impl Default for TaskLabel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskLabel {
|
impl TaskLabel {
|
||||||
|
/// Construct a new task label.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
|
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
|
||||||
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
|
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
|
||||||
|
@ -363,6 +373,7 @@ impl BackgroundExecutor {
|
||||||
|
|
||||||
/// ForegroundExecutor runs things on the main thread.
|
/// ForegroundExecutor runs things on the main thread.
|
||||||
impl ForegroundExecutor {
|
impl ForegroundExecutor {
|
||||||
|
/// Creates a new ForegroundExecutor from the given PlatformDispatcher.
|
||||||
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dispatcher,
|
dispatcher,
|
||||||
|
@ -411,13 +422,14 @@ impl<'a> Scope<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawn a future into this scope.
|
||||||
pub fn spawn<F>(&mut self, f: F)
|
pub fn spawn<F>(&mut self, f: F)
|
||||||
where
|
where
|
||||||
F: Future<Output = ()> + Send + 'a,
|
F: Future<Output = ()> + Send + 'a,
|
||||||
{
|
{
|
||||||
let tx = self.tx.clone().unwrap();
|
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.
|
// dropping this `Scope` blocks until all of the futures have resolved.
|
||||||
let f = unsafe {
|
let f = unsafe {
|
||||||
mem::transmute::<
|
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]
|
#[macro_use]
|
||||||
mod action;
|
mod action;
|
||||||
mod app;
|
mod app;
|
||||||
|
@ -58,10 +86,10 @@ pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use gpui_macros::{register_action, test, IntoElement, Render};
|
pub use gpui_macros::{register_action, test, IntoElement, Render};
|
||||||
pub use image_cache::*;
|
use image_cache::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use interactive::*;
|
pub use interactive::*;
|
||||||
pub use key_dispatch::*;
|
use key_dispatch::*;
|
||||||
pub use keymap::*;
|
pub use keymap::*;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use refineable::*;
|
pub use refineable::*;
|
||||||
|
@ -73,7 +101,7 @@ pub use smol::Timer;
|
||||||
pub use style::*;
|
pub use style::*;
|
||||||
pub use styled::*;
|
pub use styled::*;
|
||||||
pub use subscription::*;
|
pub use subscription::*;
|
||||||
pub use svg_renderer::*;
|
use svg_renderer::*;
|
||||||
pub use taffy::{AvailableSpace, LayoutId};
|
pub use taffy::{AvailableSpace, LayoutId};
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test::*;
|
pub use test::*;
|
||||||
|
@ -82,20 +110,23 @@ pub use util::arc_cow::ArcCow;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
use std::{
|
use std::{any::Any, borrow::BorrowMut};
|
||||||
any::{Any, TypeId},
|
|
||||||
borrow::BorrowMut,
|
|
||||||
};
|
|
||||||
use taffy::TaffyLayoutEngine;
|
use taffy::TaffyLayoutEngine;
|
||||||
|
|
||||||
|
/// The context trait, allows the different contexts in GPUI to be used
|
||||||
|
/// interchangeably for certain operations.
|
||||||
pub trait Context {
|
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>;
|
type Result<T>;
|
||||||
|
|
||||||
|
/// Create a new model in the app context.
|
||||||
fn new_model<T: 'static>(
|
fn new_model<T: 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||||
) -> Self::Result<Model<T>>;
|
) -> Self::Result<Model<T>>;
|
||||||
|
|
||||||
|
/// Update a model in the app context.
|
||||||
fn update_model<T, R>(
|
fn update_model<T, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
|
@ -104,6 +135,7 @@ pub trait Context {
|
||||||
where
|
where
|
||||||
T: 'static;
|
T: 'static;
|
||||||
|
|
||||||
|
/// Read a model from the app context.
|
||||||
fn read_model<T, R>(
|
fn read_model<T, R>(
|
||||||
&self,
|
&self,
|
||||||
handle: &Model<T>,
|
handle: &Model<T>,
|
||||||
|
@ -112,10 +144,12 @@ pub trait Context {
|
||||||
where
|
where
|
||||||
T: 'static;
|
T: 'static;
|
||||||
|
|
||||||
|
/// Update a window for the given handle.
|
||||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||||
where
|
where
|
||||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
||||||
|
|
||||||
|
/// Read a window off of the application context.
|
||||||
fn read_window<T, R>(
|
fn read_window<T, R>(
|
||||||
&self,
|
&self,
|
||||||
window: &WindowHandle<T>,
|
window: &WindowHandle<T>,
|
||||||
|
@ -125,7 +159,10 @@ pub trait Context {
|
||||||
T: 'static;
|
T: 'static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This trait is used for the different visual contexts in GPUI that
|
||||||
|
/// require a window to be present.
|
||||||
pub trait VisualContext: Context {
|
pub trait VisualContext: Context {
|
||||||
|
/// Construct a new view in the window referenced by this context.
|
||||||
fn new_view<V>(
|
fn new_view<V>(
|
||||||
&mut self,
|
&mut self,
|
||||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||||
|
@ -133,12 +170,14 @@ pub trait VisualContext: Context {
|
||||||
where
|
where
|
||||||
V: 'static + Render;
|
V: 'static + Render;
|
||||||
|
|
||||||
|
/// Update a view with the given callback
|
||||||
fn update_view<V: 'static, R>(
|
fn update_view<V: 'static, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
view: &View<V>,
|
view: &View<V>,
|
||||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||||
) -> Self::Result<R>;
|
) -> Self::Result<R>;
|
||||||
|
|
||||||
|
/// Replace the root view of a window with a new view.
|
||||||
fn replace_root_view<V>(
|
fn replace_root_view<V>(
|
||||||
&mut self,
|
&mut self,
|
||||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||||
|
@ -146,38 +185,42 @@ pub trait VisualContext: Context {
|
||||||
where
|
where
|
||||||
V: 'static + Render;
|
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<()>
|
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||||
where
|
where
|
||||||
V: FocusableView;
|
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<()>
|
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
|
||||||
where
|
where
|
||||||
V: ManagedView;
|
V: ManagedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait that allows models and views to be interchangeable in certain operations
|
||||||
pub trait Entity<T>: Sealed {
|
pub trait Entity<T>: Sealed {
|
||||||
|
/// The weak reference type for this entity.
|
||||||
type Weak: 'static;
|
type Weak: 'static;
|
||||||
|
|
||||||
|
/// The ID for this entity
|
||||||
fn entity_id(&self) -> EntityId;
|
fn entity_id(&self) -> EntityId;
|
||||||
|
|
||||||
|
/// Downgrade this entity to a weak reference.
|
||||||
fn downgrade(&self) -> Self::Weak;
|
fn downgrade(&self) -> Self::Weak;
|
||||||
|
|
||||||
|
/// Upgrade this entity from a weak reference.
|
||||||
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
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 trait EventEmitter<E: Any>: 'static {}
|
||||||
|
|
||||||
pub enum GlobalKey {
|
/// A helper trait for auto-implementing certain methods on contexts that
|
||||||
Numeric(usize),
|
/// can be used interchangeably.
|
||||||
View(EntityId),
|
|
||||||
Type(TypeId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BorrowAppContext {
|
pub trait BorrowAppContext {
|
||||||
fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
|
/// Set a global value on the context.
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R;
|
|
||||||
|
|
||||||
fn set_global<T: 'static>(&mut self, global: T);
|
fn set_global<T: 'static>(&mut self, global: T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,26 +228,14 @@ impl<C> BorrowAppContext for C
|
||||||
where
|
where
|
||||||
C: BorrowMut<AppContext>,
|
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) {
|
fn set_global<G: 'static>(&mut self, global: G) {
|
||||||
self.borrow_mut().set_global(global)
|
self.borrow_mut().set_global(global)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A flatten equivalent for anyhow `Result`s.
|
||||||
pub trait Flatten<T> {
|
pub trait Flatten<T> {
|
||||||
|
/// Convert this type into a simple `Result<T>`.
|
||||||
fn flatten(self) -> Result<T>;
|
fn flatten(self) -> Result<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,12 @@ use thiserror::Error;
|
||||||
use util::http::{self, HttpClient};
|
use util::http::{self, HttpClient};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||||
pub struct RenderImageParams {
|
pub(crate) struct RenderImageParams {
|
||||||
pub(crate) image_id: ImageId,
|
pub(crate) image_id: ImageId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Clone)]
|
#[derive(Debug, Error, Clone)]
|
||||||
pub enum Error {
|
pub(crate) enum Error {
|
||||||
#[error("http error: {0}")]
|
#[error("http error: {0}")]
|
||||||
Client(#[from] http::Error),
|
Client(#[from] http::Error),
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
|
@ -42,7 +42,7 @@ impl From<ImageError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ImageCache {
|
pub(crate) struct ImageCache {
|
||||||
client: Arc<dyn HttpClient>,
|
client: Arc<dyn HttpClient>,
|
||||||
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
|
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,35 @@
|
||||||
use crate::{
|
use crate::{Bounds, InputHandler, Pixels, View, ViewContext, WindowContext};
|
||||||
AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
|
|
||||||
};
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
|
/// 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`].
|
/// 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>)
|
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
||||||
-> Option<String>;
|
-> Option<String>;
|
||||||
|
|
||||||
|
/// See [`InputHandler::selected_text_range`] for details
|
||||||
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
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>>;
|
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>);
|
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||||
|
|
||||||
|
/// See [`InputHandler::replace_text_in_range`] for details
|
||||||
fn replace_text_in_range(
|
fn replace_text_in_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range: Option<Range<usize>>,
|
range: Option<Range<usize>>,
|
||||||
text: &str,
|
text: &str,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// See [`InputHandler::replace_and_mark_text_in_range`] for details
|
||||||
fn replace_and_mark_text_in_range(
|
fn replace_and_mark_text_in_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range: Option<Range<usize>>,
|
range: Option<Range<usize>>,
|
||||||
|
@ -26,6 +37,8 @@ pub trait InputHandler: 'static + Sized {
|
||||||
new_selected_range: Option<Range<usize>>,
|
new_selected_range: Option<Range<usize>>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// See [`InputHandler::bounds_for_range`] for details
|
||||||
fn bounds_for_range(
|
fn bounds_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
@ -39,7 +52,6 @@ pub trait InputHandler: 'static + Sized {
|
||||||
pub struct ElementInputHandler<V> {
|
pub struct ElementInputHandler<V> {
|
||||||
view: View<V>,
|
view: View<V>,
|
||||||
element_bounds: Bounds<Pixels>,
|
element_bounds: Bounds<Pixels>,
|
||||||
cx: AsyncWindowContext,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> ElementInputHandler<V> {
|
impl<V: 'static> ElementInputHandler<V> {
|
||||||
|
@ -47,45 +59,42 @@ impl<V: 'static> ElementInputHandler<V> {
|
||||||
/// containing view.
|
/// containing view.
|
||||||
///
|
///
|
||||||
/// [element_paint]: crate::Element::paint
|
/// [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 {
|
ElementInputHandler {
|
||||||
view,
|
view,
|
||||||
element_bounds,
|
element_bounds,
|
||||||
cx: cx.to_async(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
|
impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
|
||||||
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||||
self.view
|
self.view
|
||||||
.update(&mut self.cx, |view, cx| view.selected_text_range(cx))
|
.update(cx, |view, cx| view.selected_text_range(cx))
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
|
||||||
self.view
|
self.view.update(cx, |view, cx| view.marked_text_range(cx))
|
||||||
.update(&mut self.cx, |view, cx| view.marked_text_range(cx))
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
self.view
|
||||||
.update(&mut self.cx, |view, cx| {
|
.update(cx, |view, cx| view.text_for_range(range_utf16, cx))
|
||||||
view.text_for_range(range_utf16, cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
fn replace_text_in_range(
|
||||||
self.view
|
&mut self,
|
||||||
.update(&mut self.cx, |view, cx| {
|
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)
|
view.replace_text_in_range(replacement_range, text, cx)
|
||||||
})
|
});
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_and_mark_text_in_range(
|
fn replace_and_mark_text_in_range(
|
||||||
|
@ -93,26 +102,24 @@ impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
|
||||||
range_utf16: Option<Range<usize>>,
|
range_utf16: Option<Range<usize>>,
|
||||||
new_text: &str,
|
new_text: &str,
|
||||||
new_selected_range: Option<Range<usize>>,
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
self.view
|
self.view.update(cx, |view, cx| {
|
||||||
.update(&mut self.cx, |view, cx| {
|
|
||||||
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||||
})
|
});
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmark_text(&mut self) {
|
fn unmark_text(&mut self, cx: &mut WindowContext) {
|
||||||
self.view
|
self.view.update(cx, |view, cx| view.unmark_text(cx));
|
||||||
.update(&mut self.cx, |view, cx| view.unmark_text(cx))
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
fn bounds_for_range(
|
||||||
self.view
|
&mut self,
|
||||||
.update(&mut self.cx, |view, cx| {
|
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)
|
view.bounds_for_range(range_utf16, self.element_bounds, cx)
|
||||||
})
|
})
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,25 @@ use crate::{
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
|
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
|
||||||
|
|
||||||
|
/// An event from a platform input source.
|
||||||
pub trait InputEvent: Sealed + 'static {
|
pub trait InputEvent: Sealed + 'static {
|
||||||
|
/// Convert this event into the platform input enum.
|
||||||
fn to_platform_input(self) -> PlatformInput;
|
fn to_platform_input(self) -> PlatformInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key event from the platform.
|
||||||
pub trait KeyEvent: InputEvent {}
|
pub trait KeyEvent: InputEvent {}
|
||||||
|
|
||||||
|
/// A mouse event from the platform.
|
||||||
pub trait MouseEvent: InputEvent {}
|
pub trait MouseEvent: InputEvent {}
|
||||||
|
|
||||||
|
/// The key down event equivalent for the platform.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct KeyDownEvent {
|
pub struct KeyDownEvent {
|
||||||
|
/// The keystroke that was generated.
|
||||||
pub keystroke: Keystroke,
|
pub keystroke: Keystroke,
|
||||||
|
|
||||||
|
/// Whether the key is currently held down.
|
||||||
pub is_held: bool,
|
pub is_held: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +34,10 @@ impl InputEvent for KeyDownEvent {
|
||||||
}
|
}
|
||||||
impl KeyEvent for KeyDownEvent {}
|
impl KeyEvent for KeyDownEvent {}
|
||||||
|
|
||||||
|
/// The key up event equivalent for the platform.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KeyUpEvent {
|
pub struct KeyUpEvent {
|
||||||
|
/// The keystroke that was released.
|
||||||
pub keystroke: Keystroke,
|
pub keystroke: Keystroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +49,10 @@ impl InputEvent for KeyUpEvent {
|
||||||
}
|
}
|
||||||
impl KeyEvent for KeyUpEvent {}
|
impl KeyEvent for KeyUpEvent {}
|
||||||
|
|
||||||
|
/// The modifiers changed event equivalent for the platform.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ModifiersChangedEvent {
|
pub struct ModifiersChangedEvent {
|
||||||
|
/// The new state of the modifier keys
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,17 +76,28 @@ impl Deref for ModifiersChangedEvent {
|
||||||
/// Based on the winit enum of the same name.
|
/// Based on the winit enum of the same name.
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum TouchPhase {
|
pub enum TouchPhase {
|
||||||
|
/// The touch started.
|
||||||
Started,
|
Started,
|
||||||
|
/// The touch event is moving.
|
||||||
#[default]
|
#[default]
|
||||||
Moved,
|
Moved,
|
||||||
|
/// The touch phase has ended
|
||||||
Ended,
|
Ended,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mouse down event from the platform
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MouseDownEvent {
|
pub struct MouseDownEvent {
|
||||||
|
/// Which mouse button was pressed.
|
||||||
pub button: MouseButton,
|
pub button: MouseButton,
|
||||||
|
|
||||||
|
/// The position of the mouse on the window.
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
|
|
||||||
|
/// The modifiers that were held down when the mouse was pressed.
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
|
|
||||||
|
/// The number of times the button has been clicked.
|
||||||
pub click_count: usize,
|
pub click_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +109,19 @@ impl InputEvent for MouseDownEvent {
|
||||||
}
|
}
|
||||||
impl MouseEvent for MouseDownEvent {}
|
impl MouseEvent for MouseDownEvent {}
|
||||||
|
|
||||||
|
/// A mouse up event from the platform
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MouseUpEvent {
|
pub struct MouseUpEvent {
|
||||||
|
/// Which mouse button was released.
|
||||||
pub button: MouseButton,
|
pub button: MouseButton,
|
||||||
|
|
||||||
|
/// The position of the mouse on the window.
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
|
|
||||||
|
/// The modifiers that were held down when the mouse was released.
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
|
|
||||||
|
/// The number of times the button has been clicked.
|
||||||
pub click_count: usize,
|
pub click_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,21 +133,34 @@ impl InputEvent for MouseUpEvent {
|
||||||
}
|
}
|
||||||
impl MouseEvent for MouseUpEvent {}
|
impl MouseEvent for MouseUpEvent {}
|
||||||
|
|
||||||
|
/// A click event, generated when a mouse button is pressed and released.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ClickEvent {
|
pub struct ClickEvent {
|
||||||
|
/// The mouse event when the button was pressed.
|
||||||
pub down: MouseDownEvent,
|
pub down: MouseDownEvent,
|
||||||
|
|
||||||
|
/// The mouse event when the button was released.
|
||||||
pub up: MouseUpEvent,
|
pub up: MouseUpEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An enum representing the mouse button that was pressed.
|
||||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
|
/// The left mouse button.
|
||||||
Left,
|
Left,
|
||||||
|
|
||||||
|
/// The right mouse button.
|
||||||
Right,
|
Right,
|
||||||
|
|
||||||
|
/// The middle mouse button.
|
||||||
Middle,
|
Middle,
|
||||||
|
|
||||||
|
/// A navigation button, such as back or forward.
|
||||||
Navigate(NavigationDirection),
|
Navigate(NavigationDirection),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MouseButton {
|
impl MouseButton {
|
||||||
|
/// Get all the mouse buttons in a list.
|
||||||
pub fn all() -> Vec<Self> {
|
pub fn all() -> Vec<Self> {
|
||||||
vec![
|
vec![
|
||||||
MouseButton::Left,
|
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)]
|
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
pub enum NavigationDirection {
|
pub enum NavigationDirection {
|
||||||
|
/// The back button.
|
||||||
Back,
|
Back,
|
||||||
|
|
||||||
|
/// The forward button.
|
||||||
Forward,
|
Forward,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,10 +194,16 @@ impl Default for NavigationDirection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mouse move event from the platform
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MouseMoveEvent {
|
pub struct MouseMoveEvent {
|
||||||
|
/// The position of the mouse on the window.
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
|
|
||||||
|
/// The mouse button that was pressed, if any.
|
||||||
pub pressed_button: Option<MouseButton>,
|
pub pressed_button: Option<MouseButton>,
|
||||||
|
|
||||||
|
/// The modifiers that were held down when the mouse was moved.
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,16 +216,25 @@ impl InputEvent for MouseMoveEvent {
|
||||||
impl MouseEvent for MouseMoveEvent {}
|
impl MouseEvent for MouseMoveEvent {}
|
||||||
|
|
||||||
impl MouseMoveEvent {
|
impl MouseMoveEvent {
|
||||||
|
/// Returns true if the left mouse button is currently held down.
|
||||||
pub fn dragging(&self) -> bool {
|
pub fn dragging(&self) -> bool {
|
||||||
self.pressed_button == Some(MouseButton::Left)
|
self.pressed_button == Some(MouseButton::Left)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mouse wheel event from the platform
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct ScrollWheelEvent {
|
pub struct ScrollWheelEvent {
|
||||||
|
/// The position of the mouse on the window.
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
|
|
||||||
|
/// The change in scroll wheel position for this event.
|
||||||
pub delta: ScrollDelta,
|
pub delta: ScrollDelta,
|
||||||
|
|
||||||
|
/// The modifiers that were held down when the mouse was moved.
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
|
|
||||||
|
/// The phase of the touch event.
|
||||||
pub touch_phase: TouchPhase,
|
pub touch_phase: TouchPhase,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,9 +254,12 @@ impl Deref for ScrollWheelEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The scroll delta for a scroll wheel event.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ScrollDelta {
|
pub enum ScrollDelta {
|
||||||
|
/// An exact scroll delta in pixels.
|
||||||
Pixels(Point<Pixels>),
|
Pixels(Point<Pixels>),
|
||||||
|
/// An inexact scroll delta in lines.
|
||||||
Lines(Point<f32>),
|
Lines(Point<f32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +270,7 @@ impl Default for ScrollDelta {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollDelta {
|
impl ScrollDelta {
|
||||||
|
/// Returns true if this is a precise scroll delta in pixels.
|
||||||
pub fn precise(&self) -> bool {
|
pub fn precise(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ScrollDelta::Pixels(_) => true,
|
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> {
|
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
|
||||||
match self {
|
match self {
|
||||||
ScrollDelta::Pixels(delta) => *delta,
|
ScrollDelta::Pixels(delta) => *delta,
|
||||||
|
@ -216,6 +286,7 @@ impl ScrollDelta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combines two scroll deltas into one.
|
||||||
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
|
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
|
(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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MouseExitEvent {
|
pub struct MouseExitEvent {
|
||||||
|
/// The position of the mouse relative to the window.
|
||||||
pub position: Point<Pixels>,
|
pub position: Point<Pixels>,
|
||||||
|
/// The mouse button that was pressed, if any.
|
||||||
pub pressed_button: Option<MouseButton>,
|
pub pressed_button: Option<MouseButton>,
|
||||||
|
/// The modifiers that were held down when the mouse was moved.
|
||||||
pub modifiers: Modifiers,
|
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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||||
|
|
||||||
impl ExternalPaths {
|
impl ExternalPaths {
|
||||||
|
/// Convert this collection of paths into a slice.
|
||||||
pub fn paths(&self) -> &[PathBuf] {
|
pub fn paths(&self) -> &[PathBuf] {
|
||||||
&self.0
|
&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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FileDropEvent {
|
pub enum FileDropEvent {
|
||||||
|
/// The files have entered the window.
|
||||||
Entered {
|
Entered {
|
||||||
|
/// The position of the mouse relative to the window.
|
||||||
position: Point<Pixels>,
|
position: Point<Pixels>,
|
||||||
|
/// The paths of the files that are being dragged.
|
||||||
paths: ExternalPaths,
|
paths: ExternalPaths,
|
||||||
},
|
},
|
||||||
|
/// The files are being dragged over the window
|
||||||
Pending {
|
Pending {
|
||||||
|
/// The position of the mouse relative to the window.
|
||||||
position: Point<Pixels>,
|
position: Point<Pixels>,
|
||||||
},
|
},
|
||||||
|
/// The files have been dropped onto the window.
|
||||||
Submit {
|
Submit {
|
||||||
|
/// The position of the mouse relative to the window.
|
||||||
position: Point<Pixels>,
|
position: Point<Pixels>,
|
||||||
},
|
},
|
||||||
|
/// The user has stopped dragging the files over the window.
|
||||||
Exited,
|
Exited,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,40 +379,31 @@ impl InputEvent for FileDropEvent {
|
||||||
}
|
}
|
||||||
impl MouseEvent for FileDropEvent {}
|
impl MouseEvent for FileDropEvent {}
|
||||||
|
|
||||||
|
/// An enum corresponding to all kinds of platform input events.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PlatformInput {
|
pub enum PlatformInput {
|
||||||
|
/// A key was pressed.
|
||||||
KeyDown(KeyDownEvent),
|
KeyDown(KeyDownEvent),
|
||||||
|
/// A key was released.
|
||||||
KeyUp(KeyUpEvent),
|
KeyUp(KeyUpEvent),
|
||||||
|
/// The keyboard modifiers were changed.
|
||||||
ModifiersChanged(ModifiersChangedEvent),
|
ModifiersChanged(ModifiersChangedEvent),
|
||||||
|
/// The mouse was pressed.
|
||||||
MouseDown(MouseDownEvent),
|
MouseDown(MouseDownEvent),
|
||||||
|
/// The mouse was released.
|
||||||
MouseUp(MouseUpEvent),
|
MouseUp(MouseUpEvent),
|
||||||
|
/// The mouse was moved.
|
||||||
MouseMove(MouseMoveEvent),
|
MouseMove(MouseMoveEvent),
|
||||||
|
/// The mouse exited the window.
|
||||||
MouseExited(MouseExitEvent),
|
MouseExited(MouseExitEvent),
|
||||||
|
/// The scroll wheel was used.
|
||||||
ScrollWheel(ScrollWheelEvent),
|
ScrollWheel(ScrollWheelEvent),
|
||||||
|
/// Files were dragged and dropped onto the window.
|
||||||
FileDrop(FileDropEvent),
|
FileDrop(FileDropEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformInput {
|
impl PlatformInput {
|
||||||
pub fn position(&self) -> Option<Point<Pixels>> {
|
pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
|
||||||
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> {
|
|
||||||
match self {
|
match self {
|
||||||
PlatformInput::KeyDown { .. } => None,
|
PlatformInput::KeyDown { .. } => None,
|
||||||
PlatformInput::KeyUp { .. } => 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 {
|
match self {
|
||||||
PlatformInput::KeyDown(event) => Some(event),
|
PlatformInput::KeyDown(event) => Some(event),
|
||||||
PlatformInput::KeyUp(event) => Some(event),
|
PlatformInput::KeyUp(event) => Some(event),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch,
|
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
|
||||||
Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::FxHashMap;
|
use collections::FxHashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -13,7 +13,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct DispatchNodeId(usize);
|
pub(crate) struct DispatchNodeId(usize);
|
||||||
|
|
||||||
pub(crate) struct DispatchTree {
|
pub(crate) struct DispatchTree {
|
||||||
node_stack: Vec<DispatchNodeId>,
|
node_stack: Vec<DispatchNodeId>,
|
||||||
|
@ -36,7 +36,7 @@ pub(crate) struct DispatchNode {
|
||||||
parent: Option<DispatchNodeId>,
|
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)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DispatchActionListener {
|
pub(crate) struct DispatchActionListener {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
/// A keybinding and it's associated metadata, from the keymap.
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
pub(crate) action: Box<dyn Action>,
|
pub(crate) action: Box<dyn Action>,
|
||||||
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||||
|
@ -19,10 +20,12 @@ impl Clone for KeyBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
|
||||||
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
|
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> {
|
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||||
let context = if let Some(context) = context {
|
let context = if let Some(context) = context {
|
||||||
Some(KeyBindingContextPredicate::parse(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 {
|
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
|
||||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
||||||
// If the binding is completed, push it onto the matches list
|
// 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] {
|
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||||
self.keystrokes.as_slice()
|
self.keystrokes.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the action associated with this binding
|
||||||
pub fn action(&self) -> &dyn Action {
|
pub fn action(&self) -> &dyn Action {
|
||||||
self.action.as_ref()
|
self.action.as_ref()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@ use anyhow::{anyhow, Result};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::fmt;
|
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)]
|
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||||
|
|
||||||
|
@ -21,6 +25,11 @@ impl<'a> TryFrom<&'a str> for KeyContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn parse(source: &str) -> Result<Self> {
|
||||||
let mut context = Self::default();
|
let mut context = Self::default();
|
||||||
let source = skip_whitespace(source);
|
let source = skip_whitespace(source);
|
||||||
|
@ -53,14 +62,17 @@ impl KeyContext {
|
||||||
Self::parse_expr(source, context)
|
Self::parse_expr(source, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this context is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear this context.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.0.clear();
|
self.0.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend this context with another context.
|
||||||
pub fn extend(&mut self, other: &Self) {
|
pub fn extend(&mut self, other: &Self) {
|
||||||
for entry in &other.0 {
|
for entry in &other.0 {
|
||||||
if !self.contains(&entry.key) {
|
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) {
|
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
|
||||||
let key = identifier.into();
|
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) {
|
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
||||||
let key = key.into();
|
let key = key.into();
|
||||||
if !self.contains(&key) {
|
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 {
|
pub fn contains(&self, key: &str) -> bool {
|
||||||
self.0.iter().any(|entry| entry.key.as_ref() == key)
|
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> {
|
pub fn get(&self, key: &str) -> Option<&SharedString> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.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)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub enum KeyBindingContextPredicate {
|
pub enum KeyBindingContextPredicate {
|
||||||
|
/// A predicate that will match a given identifier.
|
||||||
Identifier(SharedString),
|
Identifier(SharedString),
|
||||||
|
/// A predicate that will match a given key-value pair.
|
||||||
Equal(SharedString, SharedString),
|
Equal(SharedString, SharedString),
|
||||||
|
/// A predicate that will match a given key-value pair not being present.
|
||||||
NotEqual(SharedString, SharedString),
|
NotEqual(SharedString, SharedString),
|
||||||
|
/// A predicate that will match a given predicate appearing below another predicate.
|
||||||
|
/// in the element tree
|
||||||
Child(
|
Child(
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
),
|
),
|
||||||
|
/// Predicate that will invert another predicate.
|
||||||
Not(Box<KeyBindingContextPredicate>),
|
Not(Box<KeyBindingContextPredicate>),
|
||||||
|
/// A predicate that will match if both of its children match.
|
||||||
And(
|
And(
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
),
|
),
|
||||||
|
/// A predicate that will match if either of its children match.
|
||||||
Or(
|
Or(
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
Box<KeyBindingContextPredicate>,
|
Box<KeyBindingContextPredicate>,
|
||||||
|
@ -138,6 +165,34 @@ pub enum KeyBindingContextPredicate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn parse(source: &str) -> Result<Self> {
|
||||||
let source = skip_whitespace(source);
|
let source = skip_whitespace(source);
|
||||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
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 {
|
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
|
||||||
let Some(context) = contexts.last() else {
|
let Some(context) = contexts.last() else {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -6,9 +6,12 @@ use std::{
|
||||||
collections::HashMap,
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||||
pub struct KeymapVersion(usize);
|
pub struct KeymapVersion(usize);
|
||||||
|
|
||||||
|
/// A collection of key bindings for the user's application.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Keymap {
|
pub struct Keymap {
|
||||||
bindings: Vec<KeyBinding>,
|
bindings: Vec<KeyBinding>,
|
||||||
|
@ -19,16 +22,19 @@ pub struct Keymap {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keymap {
|
impl Keymap {
|
||||||
|
/// Create a new keymap with the given bindings.
|
||||||
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
||||||
let mut this = Self::default();
|
let mut this = Self::default();
|
||||||
this.add_bindings(bindings);
|
this.add_bindings(bindings);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current version of the keymap.
|
||||||
pub fn version(&self) -> KeymapVersion {
|
pub fn version(&self) -> KeymapVersion {
|
||||||
self.version
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add more bindings to the keymap.
|
||||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||||
let no_action_id = (NoAction {}).type_id();
|
let no_action_id = (NoAction {}).type_id();
|
||||||
|
|
||||||
|
@ -51,6 +57,7 @@ impl Keymap {
|
||||||
self.version.0 += 1;
|
self.version.0 += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset this keymap to its initial state.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.bindings.clear();
|
self.bindings.clear();
|
||||||
self.binding_indices_by_action_id.clear();
|
self.binding_indices_by_action_id.clear();
|
||||||
|
@ -77,6 +84,7 @@ impl Keymap {
|
||||||
.filter(move |binding| binding.action().partial_eq(action))
|
.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 {
|
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
|
||||||
// If binding has a context predicate, it must match the current context,
|
// If binding has a context predicate, it must match the current context,
|
||||||
if let Some(predicate) = &binding.context_predicate {
|
if let Some(predicate) = &binding.context_predicate {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct KeystrokeMatcher {
|
pub(crate) struct KeystrokeMatcher {
|
||||||
pending_keystrokes: Vec<Keystroke>,
|
pending_keystrokes: Vec<Keystroke>,
|
||||||
keymap: Arc<Mutex<Keymap>>,
|
keymap: Arc<Mutex<Keymap>>,
|
||||||
keymap_version: KeymapVersion,
|
keymap_version: KeymapVersion,
|
||||||
|
@ -35,7 +35,7 @@ impl KeystrokeMatcher {
|
||||||
/// - KeyMatch::Complete(matches) =>
|
/// - KeyMatch::Complete(matches) =>
|
||||||
/// One or more bindings have received the necessary key presses.
|
/// One or more bindings have received the necessary key presses.
|
||||||
/// Bindings added later will take precedence over earlier bindings.
|
/// Bindings added later will take precedence over earlier bindings.
|
||||||
pub fn match_keystroke(
|
pub(crate) fn match_keystroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
keystroke: &Keystroke,
|
keystroke: &Keystroke,
|
||||||
context_stack: &[KeyContext],
|
context_stack: &[KeyContext],
|
||||||
|
@ -73,9 +73,7 @@ impl KeystrokeMatcher {
|
||||||
if !found_actions.is_empty() {
|
if !found_actions.is_empty() {
|
||||||
self.pending_keystrokes.clear();
|
self.pending_keystrokes.clear();
|
||||||
return KeyMatch::Some(found_actions);
|
return KeyMatch::Some(found_actions);
|
||||||
}
|
} else if let Some(pending_key) = pending_key {
|
||||||
|
|
||||||
if let Some(pending_key) = pending_key {
|
|
||||||
self.pending_keystrokes.push(pending_key);
|
self.pending_keystrokes.push(pending_key);
|
||||||
KeyMatch::Pending
|
KeyMatch::Pending
|
||||||
} else {
|
} 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)]
|
#[derive(Debug)]
|
||||||
pub enum KeyMatch {
|
pub enum KeyMatch {
|
||||||
None,
|
None,
|
||||||
|
@ -93,10 +95,12 @@ pub enum KeyMatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyMatch {
|
impl KeyMatch {
|
||||||
|
/// Returns true if the match is complete.
|
||||||
pub fn is_some(&self) -> bool {
|
pub fn is_some(&self) -> bool {
|
||||||
matches!(self, KeyMatch::Some(_))
|
matches!(self, KeyMatch::Some(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the matches if the match is complete.
|
||||||
pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
|
pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
|
||||||
match self {
|
match self {
|
||||||
KeyMatch::Some(matches) => Some(matches),
|
KeyMatch::Some(matches) => Some(matches),
|
||||||
|
|
|
@ -6,4 +6,4 @@ mod matcher;
|
||||||
pub use binding::*;
|
pub use binding::*;
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use keymap::*;
|
pub use keymap::*;
|
||||||
pub use matcher::*;
|
pub(crate) use matcher::*;
|
||||||
|
|
|
@ -6,10 +6,10 @@ mod mac;
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
|
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
||||||
FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
|
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
||||||
Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
|
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||||
Size, TaskLabel,
|
Scene, SharedString, Size, TaskLabel, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -34,9 +34,9 @@ use uuid::Uuid;
|
||||||
pub use app_menu::*;
|
pub use app_menu::*;
|
||||||
pub use keystroke::*;
|
pub use keystroke::*;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use mac::*;
|
pub(crate) use mac::*;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test::*;
|
pub(crate) use test::*;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -69,11 +69,10 @@ pub(crate) trait Platform: 'static {
|
||||||
fn set_display_link_output_callback(
|
fn set_display_link_output_callback(
|
||||||
&self,
|
&self,
|
||||||
display_id: DisplayId,
|
display_id: DisplayId,
|
||||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
callback: Box<dyn FnMut() + Send>,
|
||||||
);
|
);
|
||||||
fn start_display_link(&self, display_id: DisplayId);
|
fn start_display_link(&self, display_id: DisplayId);
|
||||||
fn stop_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 open_url(&self, url: &str);
|
||||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
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 mouse_position(&self) -> Point<Pixels>;
|
||||||
fn modifiers(&self) -> Modifiers;
|
fn modifiers(&self) -> Modifiers;
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
|
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
||||||
fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
|
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
fn set_title(&mut self, title: &str);
|
fn set_title(&mut self, title: &str);
|
||||||
|
@ -325,30 +324,168 @@ impl From<TileId> for etagere::AllocId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlatformInputHandler: 'static {
|
pub(crate) struct PlatformInputHandler {
|
||||||
fn selected_text_range(&mut self) -> Option<Range<usize>>;
|
cx: AsyncWindowContext,
|
||||||
fn marked_text_range(&mut self) -> Option<Range<usize>>;
|
handler: Box<dyn InputHandler>,
|
||||||
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);
|
|
||||||
|
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(
|
fn replace_and_mark_text_in_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Option<Range<usize>>,
|
range_utf16: Option<Range<usize>>,
|
||||||
new_text: &str,
|
new_text: &str,
|
||||||
new_selected_range: Option<Range<usize>>,
|
new_selected_range: Option<Range<usize>>,
|
||||||
);
|
) {
|
||||||
fn unmark_text(&mut self);
|
self.cx
|
||||||
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
|
.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)]
|
#[derive(Debug)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
|
/// The initial bounds of the window
|
||||||
pub bounds: WindowBounds,
|
pub bounds: WindowBounds,
|
||||||
|
|
||||||
|
/// The titlebar configuration of the window
|
||||||
pub titlebar: Option<TitlebarOptions>,
|
pub titlebar: Option<TitlebarOptions>,
|
||||||
|
|
||||||
|
/// Whether the window should be centered on the screen
|
||||||
pub center: bool,
|
pub center: bool,
|
||||||
|
|
||||||
|
/// Whether the window should be focused when created
|
||||||
pub focus: bool,
|
pub focus: bool,
|
||||||
|
|
||||||
|
/// Whether the window should be shown when created
|
||||||
pub show: bool,
|
pub show: bool,
|
||||||
|
|
||||||
|
/// The kind of window to create
|
||||||
pub kind: WindowKind,
|
pub kind: WindowKind,
|
||||||
|
|
||||||
|
/// Whether the window should be movable by the user
|
||||||
pub is_movable: bool,
|
pub is_movable: bool,
|
||||||
|
|
||||||
|
/// The display to create the window on
|
||||||
pub display_id: Option<DisplayId>,
|
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)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TitlebarOptions {
|
pub struct TitlebarOptions {
|
||||||
|
/// The initial title of the window
|
||||||
pub title: Option<SharedString>,
|
pub title: Option<SharedString>,
|
||||||
|
|
||||||
|
/// Whether the titlebar should appear transparent
|
||||||
pub appears_transparent: bool,
|
pub appears_transparent: bool,
|
||||||
|
|
||||||
|
/// The position of the macOS traffic light buttons
|
||||||
pub traffic_light_position: Option<Point<Pixels>>,
|
pub traffic_light_position: Option<Point<Pixels>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
/// The kind of window to create
|
||||||
pub enum Appearance {
|
|
||||||
Light,
|
|
||||||
VibrantLight,
|
|
||||||
Dark,
|
|
||||||
VibrantDark,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Appearance {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Light
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum WindowKind {
|
pub enum WindowKind {
|
||||||
|
/// A normal application window
|
||||||
Normal,
|
Normal,
|
||||||
|
|
||||||
|
/// A window that appears above all other windows, usually used for alerts or popups
|
||||||
|
/// use sparingly!
|
||||||
PopUp,
|
PopUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Which bounds algorithm to use for the initial size a window
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||||
pub enum WindowBounds {
|
pub enum WindowBounds {
|
||||||
|
/// The window should be full screen, on macOS this corresponds to the full screen feature
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
|
|
||||||
|
/// Make the window as large as the current display's size.
|
||||||
#[default]
|
#[default]
|
||||||
Maximized,
|
Maximized,
|
||||||
|
|
||||||
|
/// Set the window to the given size in pixels
|
||||||
Fixed(Bounds<GlobalPixels>),
|
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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum WindowAppearance {
|
pub enum WindowAppearance {
|
||||||
|
/// A light appearance
|
||||||
|
///
|
||||||
|
/// on macOS, this corresponds to the `aqua` appearance
|
||||||
Light,
|
Light,
|
||||||
|
|
||||||
|
/// A light appearance with vibrant colors
|
||||||
|
///
|
||||||
|
/// on macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance
|
||||||
VibrantLight,
|
VibrantLight,
|
||||||
|
|
||||||
|
/// A dark appearance
|
||||||
|
///
|
||||||
|
/// on macOS, this corresponds to the `darkAqua` appearance
|
||||||
Dark,
|
Dark,
|
||||||
|
|
||||||
|
/// A dark appearance with vibrant colors
|
||||||
|
///
|
||||||
|
/// on macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance
|
||||||
VibrantDark,
|
VibrantDark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,40 +578,102 @@ impl Default for WindowAppearance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The options that can be configured for a file dialog prompt
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct PathPromptOptions {
|
pub struct PathPromptOptions {
|
||||||
|
/// Should the prompt allow files to be selected?
|
||||||
pub files: bool,
|
pub files: bool,
|
||||||
|
/// Should the prompt allow directories to be selected?
|
||||||
pub directories: bool,
|
pub directories: bool,
|
||||||
|
/// Should the prompt allow multiple files to be selected?
|
||||||
pub multiple: bool,
|
pub multiple: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What kind of prompt styling to show
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PromptLevel {
|
pub enum PromptLevel {
|
||||||
|
/// A prompt that is shown when the user should be notified of something
|
||||||
Info,
|
Info,
|
||||||
|
|
||||||
|
/// A prompt that is shown when the user needs to be warned of a potential problem
|
||||||
Warning,
|
Warning,
|
||||||
|
|
||||||
|
/// A prompt that is shown when a critical problem has occurred
|
||||||
Critical,
|
Critical,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The style of the cursor (pointer)
|
/// The style of the cursor (pointer)
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum CursorStyle {
|
pub enum CursorStyle {
|
||||||
|
/// The default cursor
|
||||||
Arrow,
|
Arrow,
|
||||||
|
|
||||||
|
/// A text input cursor
|
||||||
|
/// corresponds to the CSS cursor value `text`
|
||||||
IBeam,
|
IBeam,
|
||||||
|
|
||||||
|
/// A crosshair cursor
|
||||||
|
/// corresponds to the CSS cursor value `crosshair`
|
||||||
Crosshair,
|
Crosshair,
|
||||||
|
|
||||||
|
/// A closed hand cursor
|
||||||
|
/// corresponds to the CSS cursor value `grabbing`
|
||||||
ClosedHand,
|
ClosedHand,
|
||||||
|
|
||||||
|
/// An open hand cursor
|
||||||
|
/// corresponds to the CSS cursor value `grab`
|
||||||
OpenHand,
|
OpenHand,
|
||||||
|
|
||||||
|
/// A pointing hand cursor
|
||||||
|
/// corresponds to the CSS cursor value `pointer`
|
||||||
PointingHand,
|
PointingHand,
|
||||||
|
|
||||||
|
/// A resize left cursor
|
||||||
|
/// corresponds to the CSS cursor value `w-resize`
|
||||||
ResizeLeft,
|
ResizeLeft,
|
||||||
|
|
||||||
|
/// A resize right cursor
|
||||||
|
/// corresponds to the CSS cursor value `e-resize`
|
||||||
ResizeRight,
|
ResizeRight,
|
||||||
|
|
||||||
|
/// A resize cursor to the left and right
|
||||||
|
/// corresponds to the CSS cursor value `col-resize`
|
||||||
ResizeLeftRight,
|
ResizeLeftRight,
|
||||||
|
|
||||||
|
/// A resize up cursor
|
||||||
|
/// corresponds to the CSS cursor value `n-resize`
|
||||||
ResizeUp,
|
ResizeUp,
|
||||||
|
|
||||||
|
/// A resize down cursor
|
||||||
|
/// corresponds to the CSS cursor value `s-resize`
|
||||||
ResizeDown,
|
ResizeDown,
|
||||||
|
|
||||||
|
/// A resize cursor directing up and down
|
||||||
|
/// corresponds to the CSS cursor value `row-resize`
|
||||||
ResizeUpDown,
|
ResizeUpDown,
|
||||||
|
|
||||||
|
/// A cursor indicating that something will disappear if moved here
|
||||||
|
/// Does not correspond to a CSS cursor value
|
||||||
DisappearingItem,
|
DisappearingItem,
|
||||||
|
|
||||||
|
/// A text input cursor for vertical layout
|
||||||
|
/// corresponds to the CSS cursor value `vertical-text`
|
||||||
IBeamCursorForVerticalLayout,
|
IBeamCursorForVerticalLayout,
|
||||||
|
|
||||||
|
/// A cursor indicating that the operation is not allowed
|
||||||
|
/// corresponds to the CSS cursor value `not-allowed`
|
||||||
OperationNotAllowed,
|
OperationNotAllowed,
|
||||||
|
|
||||||
|
/// A cursor indicating that the operation will result in a link
|
||||||
|
/// corresponds to the CSS cursor value `alias`
|
||||||
DragLink,
|
DragLink,
|
||||||
|
|
||||||
|
/// A cursor indicating that the operation will result in a copy
|
||||||
|
/// corresponds to the CSS cursor value `copy`
|
||||||
DragCopy,
|
DragCopy,
|
||||||
|
|
||||||
|
/// A cursor indicating that the operation will result in a context menu
|
||||||
|
/// corresponds to the CSS cursor value `context-menu`
|
||||||
ContextualMenu,
|
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)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
pub struct SemanticVersion {
|
pub struct SemanticVersion {
|
||||||
major: usize,
|
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)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct ClipboardItem {
|
pub struct ClipboardItem {
|
||||||
pub(crate) text: String,
|
pub(crate) text: String,
|
||||||
|
@ -508,6 +730,7 @@ pub struct ClipboardItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClipboardItem {
|
impl ClipboardItem {
|
||||||
|
/// Create a new clipboard item with the given text
|
||||||
pub fn new(text: String) -> Self {
|
pub fn new(text: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text,
|
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 {
|
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
|
||||||
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the text of the clipboard item
|
||||||
pub fn text(&self) -> &String {
|
pub fn text(&self) -> &String {
|
||||||
&self.text
|
&self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the metadata of the clipboard item
|
||||||
pub fn metadata<T>(&self) -> Option<T>
|
pub fn metadata<T>(&self) -> Option<T>
|
||||||
where
|
where
|
||||||
T: for<'a> Deserialize<'a>,
|
T: for<'a> Deserialize<'a>,
|
||||||
|
|
|
@ -1,30 +1,49 @@
|
||||||
use crate::{Action, AppContext, Platform};
|
use crate::{Action, AppContext, Platform};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
/// A menu of the application, either a main menu or a submenu
|
||||||
pub struct Menu<'a> {
|
pub struct Menu<'a> {
|
||||||
|
/// The name of the menu
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
|
|
||||||
|
/// The items in the menu
|
||||||
pub items: Vec<MenuItem<'a>>,
|
pub items: Vec<MenuItem<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The different kinds of items that can be in a menu
|
||||||
pub enum MenuItem<'a> {
|
pub enum MenuItem<'a> {
|
||||||
|
/// A separator between items
|
||||||
Separator,
|
Separator,
|
||||||
|
|
||||||
|
/// A submenu
|
||||||
Submenu(Menu<'a>),
|
Submenu(Menu<'a>),
|
||||||
|
|
||||||
|
/// An action that can be performed
|
||||||
Action {
|
Action {
|
||||||
|
/// The name of this menu item
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
|
||||||
|
/// the action to perform when this menu item is selected
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
|
|
||||||
|
/// The OS Action that corresponds to this action, if any
|
||||||
|
/// See [`OsAction`] for more information
|
||||||
os_action: Option<OsAction>,
|
os_action: Option<OsAction>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MenuItem<'a> {
|
impl<'a> MenuItem<'a> {
|
||||||
|
/// Creates a new menu item that is a separator
|
||||||
pub fn separator() -> Self {
|
pub fn separator() -> Self {
|
||||||
Self::Separator
|
Self::Separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new menu item that is a submenu
|
||||||
pub fn submenu(menu: Menu<'a>) -> Self {
|
pub fn submenu(menu: Menu<'a>) -> Self {
|
||||||
Self::Submenu(menu)
|
Self::Submenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new menu item that invokes an action
|
||||||
pub fn action(name: &'a str, action: impl Action) -> Self {
|
pub fn action(name: &'a str, action: impl Action) -> Self {
|
||||||
Self::Action {
|
Self::Action {
|
||||||
name,
|
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 {
|
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
|
||||||
Self::Action {
|
Self::Action {
|
||||||
name,
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum OsAction {
|
pub enum OsAction {
|
||||||
|
/// The 'cut' action
|
||||||
Cut,
|
Cut,
|
||||||
|
|
||||||
|
/// The 'copy' action
|
||||||
Copy,
|
Copy,
|
||||||
|
|
||||||
|
/// The 'paste' action
|
||||||
Paste,
|
Paste,
|
||||||
|
|
||||||
|
/// The 'select all' action
|
||||||
SelectAll,
|
SelectAll,
|
||||||
|
|
||||||
|
/// The 'undo' action
|
||||||
Undo,
|
Undo,
|
||||||
|
|
||||||
|
/// The 'redo' action
|
||||||
Redo,
|
Redo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,24 +3,31 @@ use serde::Deserialize;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
/// A keystroke and associated metadata generated by the platform
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||||
pub struct Keystroke {
|
pub struct Keystroke {
|
||||||
|
/// the state of the modifier keys at the time the keystroke was generated
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
|
|
||||||
/// key is the character printed on the key that was pressed
|
/// key is the character printed on the key that was pressed
|
||||||
/// e.g. for option-s, key is "s"
|
/// e.g. for option-s, key is "s"
|
||||||
pub key: String,
|
pub key: String,
|
||||||
|
|
||||||
/// ime_key is the character inserted by the IME engine when that key was pressed.
|
/// ime_key is the character inserted by the IME engine when that key was pressed.
|
||||||
/// e.g. for option-s, ime_key is "ß"
|
/// e.g. for option-s, ime_key is "ß"
|
||||||
pub ime_key: Option<String>,
|
pub ime_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keystroke {
|
impl Keystroke {
|
||||||
// When matching a key we cannot know whether the user intended to type
|
/// 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
|
/// 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),
|
/// 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
|
/// 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).
|
/// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
|
||||||
pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
|
///
|
||||||
|
/// 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();
|
let mut possibilities = SmallVec::new();
|
||||||
match self.ime_key.as_ref() {
|
match self.ime_key.as_ref() {
|
||||||
None => possibilities.push(self.clone()),
|
None => possibilities.push(self.clone()),
|
||||||
|
@ -47,7 +54,7 @@ impl Keystroke {
|
||||||
|
|
||||||
/// key syntax is:
|
/// key syntax is:
|
||||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
|
/// [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.
|
/// when matching a key with an ime_key set will be matched without it.
|
||||||
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
||||||
let mut control = false;
|
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)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||||
pub struct Modifiers {
|
pub struct Modifiers {
|
||||||
|
/// The control key
|
||||||
pub control: bool,
|
pub control: bool,
|
||||||
|
|
||||||
|
/// The alt key
|
||||||
|
/// Sometimes also known as the 'meta' key
|
||||||
pub alt: bool,
|
pub alt: bool,
|
||||||
|
|
||||||
|
/// The shift key
|
||||||
pub shift: bool,
|
pub shift: bool,
|
||||||
|
|
||||||
|
/// The command key, on macos
|
||||||
|
/// the windows key, on windows
|
||||||
pub command: bool,
|
pub command: bool,
|
||||||
|
|
||||||
|
/// The function key
|
||||||
pub function: bool,
|
pub function: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modifiers {
|
impl Modifiers {
|
||||||
|
/// Returns true if any modifier key is pressed
|
||||||
pub fn modified(&self) -> bool {
|
pub fn modified(&self) -> bool {
|
||||||
self.control || self.alt || self.shift || self.command || self.function
|
self.control || self.alt || self.shift || self.command || self.function
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ use metal_renderer::*;
|
||||||
use objc::runtime::{BOOL, NO, YES};
|
use objc::runtime::{BOOL, NO, YES};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
pub use dispatcher::*;
|
pub(crate) use dispatcher::*;
|
||||||
pub use display::*;
|
pub(crate) use display::*;
|
||||||
pub use display_linker::*;
|
pub(crate) use display_linker::*;
|
||||||
pub use metal_atlas::*;
|
pub(crate) use metal_atlas::*;
|
||||||
pub use platform::*;
|
pub(crate) use platform::*;
|
||||||
pub use text_system::*;
|
pub(crate) use text_system::*;
|
||||||
pub use window::*;
|
pub(crate) use window::*;
|
||||||
|
|
||||||
trait BoolExt {
|
trait BoolExt {
|
||||||
fn to_objc(self) -> BOOL;
|
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 }
|
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MacDispatcher {
|
pub(crate) struct MacDispatcher {
|
||||||
parker: Arc<Mutex<Parker>>,
|
parker: Arc<Mutex<Parker>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,15 @@ use anyhow::Result;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::NSScreen,
|
appkit::NSScreen,
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSDictionary, NSString},
|
foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||||
};
|
};
|
||||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||||
use core_graphics::{
|
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
||||||
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
|
|
||||||
geometry::{CGPoint, CGRect, CGSize},
|
|
||||||
};
|
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
|
pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||||
|
|
||||||
unsafe impl Send for MacDisplay {}
|
unsafe impl Send for MacDisplay {}
|
||||||
|
|
||||||
|
@ -24,11 +21,6 @@ impl MacDisplay {
|
||||||
Self::all().find(|screen| screen.id() == id)
|
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
|
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||||
/// corner is at the origin of the AppKit coordinate system.
|
/// corner is at the origin of the AppKit coordinate system.
|
||||||
pub fn primary() -> Self {
|
pub fn primary() -> Self {
|
||||||
|
@ -77,14 +69,14 @@ extern "C" {
|
||||||
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
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.
|
/// with the Y axis pointing upwards.
|
||||||
///
|
///
|
||||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||||
/// screen, with the Y axis pointing downwards.
|
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||||
pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
|
pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
|
||||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
||||||
|
|
||||||
Bounds {
|
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.
|
/// with the Y axis pointing upwards.
|
||||||
///
|
///
|
||||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||||
/// screen, with the Y axis pointing downwards.
|
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||||
pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
|
pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
|
||||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
||||||
|
|
||||||
CGRect::new(
|
NSRect::new(
|
||||||
&CGPoint::new(
|
NSPoint::new(
|
||||||
bounds.origin.x.into(),
|
bounds.origin.x.into(),
|
||||||
(primary_screen_height - bounds.origin.y - bounds.size.height).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> {
|
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let native_bounds = CGDisplayBounds(self.0);
|
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||||
display_bounds_from_native(native_bounds)
|
// 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 crate::DisplayId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
pub use sys::CVSMPTETime as SmtpeTime;
|
|
||||||
pub use sys::CVTimeStamp as VideoTimestamp;
|
|
||||||
|
|
||||||
pub(crate) struct MacDisplayLinker {
|
pub(crate) struct MacDisplayLinker {
|
||||||
links: HashMap<DisplayId, MacDisplayLink>,
|
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 {
|
impl MacDisplayLinker {
|
||||||
pub fn set_output_callback(
|
pub fn set_output_callback(
|
||||||
&mut self,
|
&mut self,
|
||||||
display_id: DisplayId,
|
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) } {
|
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||||
let callback = Arc::new(Mutex::new(output_callback));
|
let callback = Arc::new(Mutex::new(output_callback));
|
||||||
|
@ -81,11 +79,11 @@ unsafe extern "C" fn trampoline(
|
||||||
_flags_out: *mut i64,
|
_flags_out: *mut i64,
|
||||||
user_data: *mut c_void,
|
user_data: *mut c_void,
|
||||||
) -> i32 {
|
) -> 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> =
|
let output_callback: Weak<OutputCallback> =
|
||||||
Weak::from_raw(user_data as *mut OutputCallback);
|
Weak::from_raw(user_data as *mut OutputCallback);
|
||||||
if let Some(output_callback) = output_callback.upgrade() {
|
if let Some(output_callback) = output_callback.upgrade() {
|
||||||
(output_callback.lock())(current_time, output_time)
|
(output_callback.lock())()
|
||||||
}
|
}
|
||||||
mem::forget(output_callback);
|
mem::forget(output_callback);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +124,7 @@ mod sys {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct CVTimeStamp {
|
pub(crate) struct CVTimeStamp {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub video_time_scale: i32,
|
pub video_time_scale: i32,
|
||||||
pub video_time: i64,
|
pub video_time: i64,
|
||||||
|
@ -154,7 +152,7 @@ mod sys {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct CVSMPTETime {
|
pub(crate) struct CVSMPTETime {
|
||||||
pub subframes: i16,
|
pub subframes: i16,
|
||||||
pub subframe_divisor: i16,
|
pub subframe_divisor: i16,
|
||||||
pub counter: u32,
|
pub counter: u32,
|
||||||
|
|
|
@ -83,7 +83,10 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformInput {
|
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();
|
let event_type = native_event.eventType();
|
||||||
|
|
||||||
// Filter out event types that aren't in the NSEventType enum.
|
// Filter out event types that aren't in the NSEventType enum.
|
||||||
|
|
|
@ -10,10 +10,10 @@ use metal::Device;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||||
|
|
||||||
impl MetalAtlas {
|
impl MetalAtlas {
|
||||||
pub fn new(device: Device) -> Self {
|
pub(crate) fn new(device: Device) -> Self {
|
||||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||||
device: AssertSend(device),
|
device: AssertSend(device),
|
||||||
monochrome_textures: Default::default(),
|
monochrome_textures: Default::default(),
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
|
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
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,
|
background_executor: BackgroundExecutor,
|
||||||
foreground_executor: ForegroundExecutor,
|
foreground_executor: ForegroundExecutor,
|
||||||
text_system: Arc<MacTextSystem>,
|
text_system: Arc<MacTextSystem>,
|
||||||
|
@ -169,7 +169,7 @@ impl Default for MacPlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacPlatform {
|
impl MacPlatform {
|
||||||
pub fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let dispatcher = Arc::new(MacDispatcher::new());
|
let dispatcher = Arc::new(MacDispatcher::new());
|
||||||
Self(Mutex::new(MacPlatformState {
|
Self(Mutex::new(MacPlatformState {
|
||||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
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>> {
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||||
MacDisplay::all()
|
MacDisplay::all()
|
||||||
.map(|screen| Rc::new(screen) as Rc<_>)
|
.map(|screen| Rc::new(screen) as Rc<_>)
|
||||||
|
@ -504,7 +500,7 @@ impl Platform for MacPlatform {
|
||||||
fn set_display_link_output_callback(
|
fn set_display_link_output_callback(
|
||||||
&self,
|
&self,
|
||||||
display_id: DisplayId,
|
display_id: DisplayId,
|
||||||
callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
callback: Box<dyn FnMut() + Send>,
|
||||||
) {
|
) {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -41,7 +41,7 @@ use super::open_type;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
const kCGImageAlphaOnly: u32 = 7;
|
const kCGImageAlphaOnly: u32 = 7;
|
||||||
|
|
||||||
pub struct MacTextSystem(RwLock<MacTextSystemState>);
|
pub(crate) struct MacTextSystem(RwLock<MacTextSystemState>);
|
||||||
|
|
||||||
struct MacTextSystemState {
|
struct MacTextSystemState {
|
||||||
memory_source: MemSource,
|
memory_source: MemSource,
|
||||||
|
@ -54,7 +54,7 @@ struct MacTextSystemState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacTextSystem {
|
impl MacTextSystem {
|
||||||
pub fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self(RwLock::new(MacTextSystemState {
|
Self(RwLock::new(MacTextSystemState {
|
||||||
memory_source: MemSource::empty(),
|
memory_source: MemSource::empty(),
|
||||||
system_source: SystemSource::new(),
|
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::{
|
use crate::{
|
||||||
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
|
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||||
FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers,
|
Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
|
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||||
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
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(
|
point(
|
||||||
px(position.x as f32),
|
px(position.x as f32),
|
||||||
// MacOS screen coordinates are relative to bottom left
|
// MacOS screen coordinates are relative to bottom left
|
||||||
|
@ -327,7 +327,7 @@ struct MacWindowState {
|
||||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||||
close_callback: Option<Box<dyn FnOnce()>>,
|
close_callback: Option<Box<dyn FnOnce()>>,
|
||||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
input_handler: Option<PlatformInputHandler>,
|
||||||
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
||||||
last_key_equivalent: Option<KeyDownEvent>,
|
last_key_equivalent: Option<KeyDownEvent>,
|
||||||
synthetic_drag_counter: usize,
|
synthetic_drag_counter: usize,
|
||||||
|
@ -411,10 +411,8 @@ impl MacWindowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(&self) -> Bounds<GlobalPixels> {
|
fn frame(&self) -> Bounds<GlobalPixels> {
|
||||||
unsafe {
|
let frame = unsafe { NSWindow::frame(self.native_window) };
|
||||||
let frame = NSWindow::frame(self.native_window);
|
global_bounds_from_ns_rect(frame)
|
||||||
display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
|
@ -448,7 +446,7 @@ impl MacWindowState {
|
||||||
|
|
||||||
unsafe impl Send for MacWindowState {}
|
unsafe impl Send for MacWindowState {}
|
||||||
|
|
||||||
pub struct MacWindow(Arc<Mutex<MacWindowState>>);
|
pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
|
||||||
|
|
||||||
impl MacWindow {
|
impl MacWindow {
|
||||||
pub fn open(
|
pub fn open(
|
||||||
|
@ -650,11 +648,11 @@ impl MacWindow {
|
||||||
WindowBounds::Fixed(bounds) => {
|
WindowBounds::Fixed(bounds) => {
|
||||||
let display_bounds = display.bounds();
|
let display_bounds = display.bounds();
|
||||||
let frame = if bounds.intersects(&display_bounds) {
|
let frame = if bounds.intersects(&display_bounds) {
|
||||||
display_bounds_to_native(bounds)
|
global_bounds_to_ns_rect(bounds)
|
||||||
} else {
|
} 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
|
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);
|
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()
|
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>
|
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut dyn PlatformInputHandler) -> R,
|
F: FnOnce(&mut PlatformInputHandler) -> R,
|
||||||
{
|
{
|
||||||
let window_state = unsafe { get_window_state(window) };
|
let window_state = unsafe { get_window_state(window) };
|
||||||
let mut lock = window_state.as_ref().lock();
|
let mut lock = window_state.as_ref().lock();
|
||||||
if let Some(mut input_handler) = lock.input_handler.take() {
|
if let Some(mut input_handler) = lock.input_handler.take() {
|
||||||
drop(lock);
|
drop(lock);
|
||||||
let result = f(input_handler.as_mut());
|
let result = f(&mut input_handler);
|
||||||
window_state.lock().input_handler = Some(input_handler);
|
window_state.lock().input_handler = Some(input_handler);
|
||||||
Some(result)
|
Some(result)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,7 +8,7 @@ use objc::{msg_send, sel, sel_impl};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
|
||||||
impl WindowAppearance {
|
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];
|
let name: id = msg_send![appearance, name];
|
||||||
if name == NSAppearanceNameVibrantLight {
|
if name == NSAppearanceNameVibrantLight {
|
||||||
Self::VibrantLight
|
Self::VibrantLight
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod display;
|
||||||
mod platform;
|
mod platform;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
pub use dispatcher::*;
|
pub(crate) use dispatcher::*;
|
||||||
pub use display::*;
|
pub(crate) use display::*;
|
||||||
pub use platform::*;
|
pub(crate) use platform::*;
|
||||||
pub use window::*;
|
pub(crate) use window::*;
|
||||||
|
|
|
@ -18,6 +18,7 @@ use util::post_inc;
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
struct TestDispatcherId(usize);
|
struct TestDispatcherId(usize);
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
pub struct TestDispatcher {
|
pub struct TestDispatcher {
|
||||||
id: TestDispatcherId,
|
id: TestDispatcherId,
|
||||||
state: Arc<Mutex<TestDispatcherState>>,
|
state: Arc<Mutex<TestDispatcherState>>,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use anyhow::{Ok, Result};
|
||||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TestDisplay {
|
pub(crate) struct TestDisplay {
|
||||||
id: DisplayId,
|
id: DisplayId,
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
bounds: Bounds<GlobalPixels>,
|
bounds: Bounds<GlobalPixels>,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// TestPlatform implements the Platform trait for use in tests.
|
/// TestPlatform implements the Platform trait for use in tests.
|
||||||
pub struct TestPlatform {
|
pub(crate) struct TestPlatform {
|
||||||
background_executor: BackgroundExecutor,
|
background_executor: BackgroundExecutor,
|
||||||
foreground_executor: ForegroundExecutor,
|
foreground_executor: ForegroundExecutor,
|
||||||
|
|
||||||
|
@ -178,20 +178,9 @@ impl Platform for TestPlatform {
|
||||||
fn set_display_link_output_callback(
|
fn set_display_link_output_callback(
|
||||||
&self,
|
&self,
|
||||||
_display_id: DisplayId,
|
_display_id: DisplayId,
|
||||||
mut callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
|
mut callback: Box<dyn FnMut() + Send>,
|
||||||
) {
|
) {
|
||||||
let timestamp = crate::VideoTimestamp {
|
callback()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_display_link(&self, _display_id: DisplayId) {}
|
fn start_display_link(&self, _display_id: DisplayId) {}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
sync::{self, Arc},
|
sync::{self, Arc},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TestWindowState {
|
pub(crate) struct TestWindowState {
|
||||||
pub(crate) bounds: WindowBounds,
|
pub(crate) bounds: WindowBounds,
|
||||||
pub(crate) handle: AnyWindowHandle,
|
pub(crate) handle: AnyWindowHandle,
|
||||||
display: Rc<dyn PlatformDisplay>,
|
display: Rc<dyn PlatformDisplay>,
|
||||||
|
@ -23,11 +23,11 @@ pub struct TestWindowState {
|
||||||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
moved_callback: Option<Box<dyn FnMut()>>,
|
moved_callback: Option<Box<dyn FnMut()>>,
|
||||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
input_handler: Option<PlatformInputHandler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
|
pub(crate) struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
|
||||||
|
|
||||||
impl TestWindow {
|
impl TestWindow {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -117,9 +117,6 @@ impl TestWindow {
|
||||||
|
|
||||||
self.0.lock().input_handler = Some(input_handler);
|
self.0.lock().input_handler = Some(input_handler);
|
||||||
}
|
}
|
||||||
pub fn edited(&self) -> bool {
|
|
||||||
self.0.lock().edited
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformWindow for TestWindow {
|
impl PlatformWindow for TestWindow {
|
||||||
|
@ -163,11 +160,11 @@ impl PlatformWindow for TestWindow {
|
||||||
self
|
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);
|
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()
|
self.0.lock().input_handler.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,12 +266,12 @@ impl PlatformWindow for TestWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestAtlasState {
|
pub(crate) struct TestAtlasState {
|
||||||
next_id: u32,
|
next_id: u32,
|
||||||
tiles: HashMap<AtlasKey, AtlasTile>,
|
tiles: HashMap<AtlasKey, AtlasTile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestAtlas(Mutex<TestAtlasState>);
|
pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
|
||||||
|
|
||||||
impl TestAtlas {
|
impl TestAtlas {
|
||||||
pub fn new() -> Self {
|
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::{
|
pub use crate::{
|
||||||
util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
|
util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
|
||||||
InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
|
InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
|
||||||
|
|
|
@ -10,12 +10,12 @@ pub(crate) type PointF = Point<f32>;
|
||||||
#[allow(non_camel_case_types, unused)]
|
#[allow(non_camel_case_types, unused)]
|
||||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||||
|
|
||||||
pub type LayerId = u32;
|
pub(crate) type LayerId = u32;
|
||||||
pub type DrawOrder = u32;
|
pub(crate) type DrawOrder = u32;
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ViewId {
|
pub(crate) struct ViewId {
|
||||||
low_bits: u32,
|
low_bits: u32,
|
||||||
high_bits: u32,
|
high_bits: u32,
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ impl From<ViewId> for EntityId {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Scene {
|
pub(crate) struct Scene {
|
||||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
||||||
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
||||||
pub(crate) shadows: Vec<Shadow>,
|
pub(crate) shadows: Vec<Shadow>,
|
||||||
|
@ -153,49 +153,49 @@ impl Scene {
|
||||||
for shadow in prev_scene.shadows.drain(..) {
|
for shadow in prev_scene.shadows.drain(..) {
|
||||||
if views.contains(&shadow.view_id.into()) {
|
if views.contains(&shadow.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
|
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(..) {
|
for quad in prev_scene.quads.drain(..) {
|
||||||
if views.contains(&quad.view_id.into()) {
|
if views.contains(&quad.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&quad.layer_id];
|
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(..) {
|
for path in prev_scene.paths.drain(..) {
|
||||||
if views.contains(&path.view_id.into()) {
|
if views.contains(&path.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&path.layer_id];
|
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(..) {
|
for underline in prev_scene.underlines.drain(..) {
|
||||||
if views.contains(&underline.view_id.into()) {
|
if views.contains(&underline.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&underline.layer_id];
|
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(..) {
|
for sprite in prev_scene.monochrome_sprites.drain(..) {
|
||||||
if views.contains(&sprite.view_id.into()) {
|
if views.contains(&sprite.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
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(..) {
|
for sprite in prev_scene.polychrome_sprites.drain(..) {
|
||||||
if views.contains(&sprite.view_id.into()) {
|
if views.contains(&sprite.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
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(..) {
|
for surface in prev_scene.surfaces.drain(..) {
|
||||||
if views.contains(&surface.view_id.into()) {
|
if views.contains(&surface.view_id.into()) {
|
||||||
let order = &prev_scene.orders_by_layer[&surface.layer_id];
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||||
pub enum PrimitiveKind {
|
pub(crate) enum PrimitiveKind {
|
||||||
Shadow,
|
Shadow,
|
||||||
#[default]
|
#[default]
|
||||||
Quad,
|
Quad,
|
||||||
|
@ -495,7 +495,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Quad {
|
pub(crate) struct Quad {
|
||||||
pub view_id: ViewId,
|
pub view_id: ViewId,
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
|
@ -527,7 +527,7 @@ impl From<Quad> for Primitive {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Underline {
|
pub(crate) struct Underline {
|
||||||
pub view_id: ViewId,
|
pub view_id: ViewId,
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
|
@ -558,7 +558,7 @@ impl From<Underline> for Primitive {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Shadow {
|
pub(crate) struct Shadow {
|
||||||
pub view_id: ViewId,
|
pub view_id: ViewId,
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
|
@ -655,7 +655,7 @@ impl From<PolychromeSprite> for Primitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Surface {
|
pub(crate) struct Surface {
|
||||||
pub view_id: ViewId,
|
pub view_id: ViewId,
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
pub order: DrawOrder,
|
pub order: DrawOrder,
|
||||||
|
@ -685,6 +685,7 @@ impl From<Surface> for Primitive {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct PathId(pub(crate) usize);
|
pub(crate) struct PathId(pub(crate) usize);
|
||||||
|
|
||||||
|
/// A line made up of a series of vertices and control points.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Path<P: Clone + Default + Debug> {
|
pub struct Path<P: Clone + Default + Debug> {
|
||||||
pub(crate) id: PathId,
|
pub(crate) id: PathId,
|
||||||
|
@ -701,6 +702,7 @@ pub struct Path<P: Clone + Default + Debug> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Path<Pixels> {
|
impl Path<Pixels> {
|
||||||
|
/// Create a new path with the given starting point.
|
||||||
pub fn new(start: Point<Pixels>) -> Self {
|
pub fn new(start: Point<Pixels>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PathId(0),
|
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> {
|
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||||
Path {
|
Path {
|
||||||
id: self.id,
|
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>) {
|
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||||
self.contour_count += 1;
|
self.contour_count += 1;
|
||||||
if self.contour_count > 1 {
|
if self.contour_count > 1 {
|
||||||
|
@ -751,6 +755,7 @@ impl Path<Pixels> {
|
||||||
self.current = to;
|
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>) {
|
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||||
self.contour_count += 1;
|
self.contour_count += 1;
|
||||||
if self.contour_count > 1 {
|
if self.contour_count > 1 {
|
||||||
|
@ -833,7 +838,7 @@ impl From<Path<ScaledPixels>> for Primitive {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[repr(C)]
|
#[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) xy_position: Point<P>,
|
||||||
pub(crate) st_position: Point<f32>,
|
pub(crate) st_position: Point<f32>,
|
||||||
pub(crate) content_mask: ContentMask<P>,
|
pub(crate) content_mask: ContentMask<P>,
|
||||||
|
@ -850,4 +855,4 @@ impl PathVertex<Pixels> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[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 std::{borrow::Borrow, sync::Arc};
|
||||||
use util::arc_cow::ArcCow;
|
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)]
|
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||||
pub struct SharedString(ArcCow<'static, str>);
|
pub struct SharedString(ArcCow<'static, str>);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{iter, mem, ops::Range};
|
use std::{iter, mem, ops::Range};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds,
|
black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, Bounds, ContentMask, Corners,
|
||||||
ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement,
|
CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font,
|
||||||
Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
|
||||||
SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
|
SharedString, Size, SizeRefinement, Styled, TextRun,
|
||||||
};
|
};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use refineable::{Cascade, Refineable};
|
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.
|
/// Paints the background of an element styled with this style.
|
||||||
pub fn paint(
|
pub fn paint(
|
||||||
&self,
|
&self,
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
continuation: impl FnOnce(&mut WindowContext),
|
continuation: impl FnOnce(&mut ElementContext),
|
||||||
) {
|
) {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if self.debug_below {
|
if self.debug_below {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
|
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
|
||||||
DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length,
|
DefiniteLength, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, Position,
|
||||||
Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
|
SharedString, StyleRefinement, Visibility, WhiteSpace,
|
||||||
};
|
};
|
||||||
use crate::{BoxShadow, TextStyleRefinement};
|
use crate::{BoxShadow, TextStyleRefinement};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use taffy::style::Overflow;
|
use taffy::style::{Display, Overflow};
|
||||||
|
|
||||||
pub trait Styled: Sized {
|
pub trait Styled: Sized {
|
||||||
fn style(&mut self) -> &mut StyleRefinement;
|
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]
|
#[must_use]
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
|
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subscription {
|
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) {
|
pub fn detach(mut self) {
|
||||||
self.unsubscribe.take();
|
self.unsubscribe.take();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ use anyhow::anyhow;
|
||||||
use std::{hash::Hash, sync::Arc};
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Hash, Eq)]
|
#[derive(Clone, PartialEq, Hash, Eq)]
|
||||||
pub struct RenderSvgParams {
|
pub(crate) struct RenderSvgParams {
|
||||||
pub(crate) path: SharedString,
|
pub(crate) path: SharedString,
|
||||||
pub(crate) size: Size<DevicePixels>,
|
pub(crate) size: Size<DevicePixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SvgRenderer {
|
pub(crate) struct SvgRenderer {
|
||||||
asset_source: Arc<dyn AssetSource>,
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct LayoutId(NodeId);
|
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)]
|
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
|
||||||
pub enum AvailableSpace {
|
pub enum AvailableSpace {
|
||||||
/// The amount of space available is the specified number of pixels
|
/// The amount of space available is the specified number of pixels
|
||||||
|
|
|
@ -34,6 +34,9 @@ use std::{
|
||||||
panic::{self, RefUnwindSafe},
|
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(
|
pub fn run_test(
|
||||||
mut num_iterations: u64,
|
mut num_iterations: u64,
|
||||||
max_retries: usize,
|
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> {
|
pub struct Observation<T> {
|
||||||
rx: channel::Receiver<T>,
|
rx: channel::Receiver<T>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
|
|
|
@ -377,7 +377,7 @@ impl TextSystem {
|
||||||
Ok(lines)
|
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)
|
self.line_layout_cache.finish_frame(reused_views)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
black, fill, point, px, size, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result,
|
black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result,
|
||||||
SharedString, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
SharedString, UnderlineStyle, WrapBoundary, WrappedLineLayout,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -24,6 +24,7 @@ pub struct ShapedLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapedLine {
|
impl ShapedLine {
|
||||||
|
/// The length of the line in utf-8 bytes.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.layout.len
|
self.layout.len
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,7 @@ impl ShapedLine {
|
||||||
&self,
|
&self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
paint_line(
|
paint_line(
|
||||||
origin,
|
origin,
|
||||||
|
@ -65,7 +66,7 @@ impl WrappedLine {
|
||||||
&self,
|
&self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
paint_line(
|
paint_line(
|
||||||
origin,
|
origin,
|
||||||
|
@ -86,7 +87,7 @@ fn paint_line(
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
decoration_runs: &[DecorationRun],
|
decoration_runs: &[DecorationRun],
|
||||||
wrap_boundaries: &[WrapBoundary],
|
wrap_boundaries: &[WrapBoundary],
|
||||||
cx: &mut WindowContext<'_>,
|
cx: &mut ElementContext<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
|
||||||
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
let baseline_offset = point(px(0.), padding_top + layout.ascent);
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
|
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
|
||||||
Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
|
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
|
||||||
IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle,
|
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
|
||||||
ViewContext, VisualContext, WeakModel, WindowContext,
|
TextStyle, ViewContext, VisualContext, WeakModel,
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -96,7 +94,7 @@ impl<V: Render> Element for View<V> {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: Option<Self::State>,
|
_state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
cx.with_view_id(self.entity_id(), |cx| {
|
cx.with_view_id(self.entity_id(), |cx| {
|
||||||
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
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));
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AnyView {
|
pub struct AnyView {
|
||||||
model: AnyModel,
|
model: AnyModel,
|
||||||
request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
|
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||||
cache: bool,
|
cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +250,7 @@ impl AnyView {
|
||||||
&self,
|
&self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
available_space: Size<AvailableSpace>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) {
|
) {
|
||||||
cx.paint_view(self.entity_id(), |cx| {
|
cx.paint_view(self.entity_id(), |cx| {
|
||||||
cx.with_absolute_element_offset(origin, |cx| {
|
cx.with_absolute_element_offset(origin, |cx| {
|
||||||
|
@ -280,7 +278,7 @@ impl Element for AnyView {
|
||||||
fn request_layout(
|
fn request_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: Option<Self::State>,
|
state: Option<Self::State>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, Self::State) {
|
) -> (LayoutId, Self::State) {
|
||||||
cx.with_view_id(self.entity_id(), |cx| {
|
cx.with_view_id(self.entity_id(), |cx| {
|
||||||
if self.cache {
|
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| {
|
cx.paint_view(self.entity_id(), |cx| {
|
||||||
if !self.cache {
|
if !self.cache {
|
||||||
state.element.take().unwrap().paint(cx);
|
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.
|
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||||
pub struct AnyWeakView {
|
pub struct AnyWeakView {
|
||||||
model: AnyWeakModel,
|
model: AnyWeakModel,
|
||||||
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
|
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnyWeakView {
|
impl AnyWeakView {
|
||||||
|
@ -404,11 +402,11 @@ impl std::fmt::Debug for AnyWeakView {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod any_view {
|
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>(
|
pub(crate) fn request_layout<V: 'static + Render>(
|
||||||
view: &AnyView,
|
view: &AnyView,
|
||||||
cx: &mut WindowContext,
|
cx: &mut ElementContext,
|
||||||
) -> (LayoutId, AnyElement) {
|
) -> (LayoutId, AnyElement) {
|
||||||
let view = view.clone().downcast::<V>().unwrap();
|
let view = view.clone().downcast::<V>().unwrap();
|
||||||
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
|
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 {
|
cx.spawn(|mut cx| async move {
|
||||||
let (journal_dir, entry_path) = create_entry.await?;
|
let (journal_dir, entry_path) = create_entry.await?;
|
||||||
let (workspace, _) = cx
|
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?;
|
.await?;
|
||||||
|
|
||||||
let opened = workspace
|
let opened = workspace
|
||||||
|
|
|
@ -3007,7 +3007,7 @@ impl BufferSnapshot {
|
||||||
groups.sort_by(|(id_a, group_a), (id_b, group_b)| {
|
groups.sort_by(|(id_a, group_a), (id_b, group_b)| {
|
||||||
let a_start = &group_a.entries[group_a.primary_ix].range.start;
|
let a_start = &group_a.entries[group_a.primary_ix].range.start;
|
||||||
let b_start = &group_b.entries[group_b.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
|
groups
|
||||||
|
|
|
@ -194,7 +194,7 @@ impl DiagnosticSet {
|
||||||
.range
|
.range
|
||||||
.start
|
.start
|
||||||
.cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
|
.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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CodeLabel {
|
pub struct CodeLabel {
|
||||||
|
/// The text to display.
|
||||||
pub text: String,
|
pub text: String,
|
||||||
|
/// Syntax highlighting runs.
|
||||||
pub runs: Vec<(Range<usize>, HighlightId)>,
|
pub runs: Vec<(Range<usize>, HighlightId)>,
|
||||||
|
/// The portion of the text that should be used in fuzzy filtering.
|
||||||
pub filter_range: Range<usize>,
|
pub filter_range: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,7 +852,7 @@ impl LanguageRegistry {
|
||||||
let mut state = self.state.write();
|
let mut state = self.state.write();
|
||||||
state.theme = Some(theme.clone());
|
state.theme = Some(theme.clone());
|
||||||
for language in &state.languages {
|
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>) {
|
fn add(&mut self, language: Arc<Language>) {
|
||||||
if let Some(theme) = self.theme.as_ref() {
|
if let Some(theme) = self.theme.as_ref() {
|
||||||
language.set_theme(&theme.syntax());
|
language.set_theme(theme.syntax());
|
||||||
}
|
}
|
||||||
self.languages.push(language);
|
self.languages.push(language);
|
||||||
self.version += 1;
|
self.version += 1;
|
||||||
|
|
|
@ -91,6 +91,8 @@ pub struct LanguageSettings {
|
||||||
pub extend_comment_on_newline: bool,
|
pub extend_comment_on_newline: bool,
|
||||||
/// Inlay hint related settings.
|
/// Inlay hint related settings.
|
||||||
pub inlay_hints: InlayHintSettings,
|
pub inlay_hints: InlayHintSettings,
|
||||||
|
/// Whether to automatically close brackets.
|
||||||
|
pub use_autoclose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
||||||
|
@ -208,6 +210,11 @@ pub struct LanguageSettingsContent {
|
||||||
/// Inlay hint related settings.
|
/// Inlay hint related settings.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub inlay_hints: Option<InlayHintSettings>,
|
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.
|
/// The contents of the GitHub Copilot settings.
|
||||||
|
@ -421,7 +428,7 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
let mut languages = HashMap::default();
|
let mut languages = HashMap::default();
|
||||||
for (language_name, settings) in &default_value.languages {
|
for (language_name, settings) in &default_value.languages {
|
||||||
let mut language_settings = defaults.clone();
|
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);
|
languages.insert(language_name.clone(), language_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +468,7 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
languages
|
languages
|
||||||
.entry(language_name.clone())
|
.entry(language_name.clone())
|
||||||
.or_insert_with(|| defaults.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.tab_size, src.tab_size);
|
||||||
merge(&mut settings.hard_tabs, src.hard_tabs);
|
merge(&mut settings.hard_tabs, src.hard_tabs);
|
||||||
merge(&mut settings.soft_wrap, src.soft_wrap);
|
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.show_wrap_guides, src.show_wrap_guides);
|
||||||
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
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 current_language = None;
|
||||||
let mut list_stack = Vec::new();
|
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();
|
let prev_len = text.len();
|
||||||
match event {
|
match event {
|
||||||
Event::Text(t) => {
|
Event::Text(t) => {
|
||||||
|
|
|
@ -283,7 +283,7 @@ impl SyntaxSnapshot {
|
||||||
depth,
|
depth,
|
||||||
position: edit_range.start,
|
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);
|
let slice = cursor.slice(&target, Bias::Left, text);
|
||||||
layers.append(slice, text);
|
layers.append(slice, text);
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,7 @@ impl SyntaxSnapshot {
|
||||||
cursor.next(text);
|
cursor.next(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
layers.append(cursor.suffix(&text), &text);
|
layers.append(cursor.suffix(text), text);
|
||||||
drop(cursor);
|
drop(cursor);
|
||||||
self.layers = layers;
|
self.layers = layers;
|
||||||
}
|
}
|
||||||
|
@ -433,7 +433,7 @@ impl SyntaxSnapshot {
|
||||||
|
|
||||||
let max_depth = self.layers.summary().max_depth;
|
let max_depth = self.layers.summary().max_depth;
|
||||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
||||||
cursor.next(&text);
|
cursor.next(text);
|
||||||
let mut layers = SumTree::new();
|
let mut layers = SumTree::new();
|
||||||
|
|
||||||
let mut changed_regions = ChangeRegionSet::default();
|
let mut changed_regions = ChangeRegionSet::default();
|
||||||
|
@ -471,17 +471,17 @@ impl SyntaxSnapshot {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut done = cursor.item().is_none();
|
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;
|
done = true;
|
||||||
|
|
||||||
let bounded_position = SyntaxLayerPositionBeforeChange {
|
let bounded_position = SyntaxLayerPositionBeforeChange {
|
||||||
position: position.clone(),
|
position: position.clone(),
|
||||||
change: changed_regions.start_position(),
|
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);
|
let slice = cursor.slice(&bounded_position, Bias::Left, text);
|
||||||
if !slice.is_empty() {
|
if !slice.is_empty() {
|
||||||
layers.append(slice, &text);
|
layers.append(slice, text);
|
||||||
if changed_regions.prune(cursor.end(text), text) {
|
if changed_regions.prune(cursor.end(text), text) {
|
||||||
done = false;
|
done = false;
|
||||||
}
|
}
|
||||||
|
@ -491,7 +491,7 @@ impl SyntaxSnapshot {
|
||||||
while position.cmp(&cursor.end(text), text).is_gt() {
|
while position.cmp(&cursor.end(text), text).is_gt() {
|
||||||
let Some(layer) = cursor.item() else { break };
|
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 {
|
if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
|
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
|
||||||
|
@ -529,7 +529,7 @@ impl SyntaxSnapshot {
|
||||||
if layer.range.to_offset(text) == (step_start_byte..step_end_byte)
|
if layer.range.to_offset(text) == (step_start_byte..step_end_byte)
|
||||||
&& layer.content.language_id() == step.language.id()
|
&& layer.content.language_id() == step.language.id()
|
||||||
{
|
{
|
||||||
cursor.next(&text);
|
cursor.next(text);
|
||||||
} else {
|
} else {
|
||||||
old_layer = None;
|
old_layer = None;
|
||||||
}
|
}
|
||||||
|
@ -561,7 +561,7 @@ impl SyntaxSnapshot {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"existing layer. language:{}, start:{:?}, ranges:{:?}",
|
"existing layer. language:{}, start:{:?}, ranges:{:?}",
|
||||||
language.name(),
|
language.name(),
|
||||||
LogPoint(layer_start.to_point(&text)),
|
LogPoint(layer_start.to_point(text)),
|
||||||
LogIncludedRanges(&old_tree.included_ranges())
|
LogIncludedRanges(&old_tree.included_ranges())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -584,7 +584,7 @@ impl SyntaxSnapshot {
|
||||||
insert_newlines_between_ranges(
|
insert_newlines_between_ranges(
|
||||||
changed_indices,
|
changed_indices,
|
||||||
&mut included_ranges,
|
&mut included_ranges,
|
||||||
&text,
|
text,
|
||||||
step_start_byte,
|
step_start_byte,
|
||||||
step_start_point,
|
step_start_point,
|
||||||
);
|
);
|
||||||
|
@ -701,7 +701,7 @@ impl SyntaxSnapshot {
|
||||||
range: step.range,
|
range: step.range,
|
||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
&text,
|
text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -860,7 +860,7 @@ impl<'a> SyntaxMapCaptures<'a> {
|
||||||
Some(grammar) => grammar,
|
Some(grammar) => grammar,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let query = match query(&grammar) {
|
let query = match query(grammar) {
|
||||||
Some(query) => query,
|
Some(query) => query,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
@ -978,7 +978,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||||
Some(grammar) => grammar,
|
Some(grammar) => grammar,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let query = match query(&grammar) {
|
let query = match query(grammar) {
|
||||||
Some(query) => query,
|
Some(query) => query,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
@ -1087,7 +1087,7 @@ impl<'a> SyntaxMapMatchesLayer<'a> {
|
||||||
fn advance(&mut self) {
|
fn advance(&mut self) {
|
||||||
if let Some(mat) = self.matches.next() {
|
if let Some(mat) = self.matches.next() {
|
||||||
self.next_captures.clear();
|
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.next_pattern_index = mat.pattern_index;
|
||||||
self.has_next = true;
|
self.has_next = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1517,7 +1517,7 @@ impl Eq for ParseStep {}
|
||||||
|
|
||||||
impl PartialOrd for ParseStep {
|
impl PartialOrd for ParseStep {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
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) {
|
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
|
||||||
handler(
|
handler(
|
||||||
msg.id,
|
msg.id,
|
||||||
&msg.params.map(|params| params.get()).unwrap_or("null"),
|
msg.params.map(|params| params.get()).unwrap_or("null"),
|
||||||
cx.clone(),
|
cx.clone(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2316,7 +2316,7 @@ impl MultiBufferSnapshot {
|
||||||
&self,
|
&self,
|
||||||
point: T,
|
point: T,
|
||||||
) -> Option<(&BufferSnapshot, usize)> {
|
) -> Option<(&BufferSnapshot, usize)> {
|
||||||
let offset = point.to_offset(&self);
|
let offset = point.to_offset(self);
|
||||||
let mut cursor = self.excerpts.cursor::<usize>();
|
let mut cursor = self.excerpts.cursor::<usize>();
|
||||||
cursor.seek(&offset, Bias::Right, &());
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
if cursor.item().is_none() {
|
if cursor.item().is_none() {
|
||||||
|
@ -3694,7 +3694,7 @@ impl ExcerptId {
|
||||||
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
|
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
|
||||||
let a = snapshot.excerpt_locator_for_id(*self);
|
let a = snapshot.excerpt_locator_for_id(*self);
|
||||||
let b = snapshot.excerpt_locator_for_id(*other);
|
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()
|
.iter()
|
||||||
.map(language::proto::serialize_completion)
|
.map(language::proto::serialize_completion)
|
||||||
.collect(),
|
.collect(),
|
||||||
version: serialize_version(&buffer_version),
|
version: serialize_version(buffer_version),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1672,7 +1672,7 @@ impl LspCommand for GetCodeActions {
|
||||||
.iter()
|
.iter()
|
||||||
.map(language::proto::serialize_code_action)
|
.map(language::proto::serialize_code_action)
|
||||||
.collect(),
|
.collect(),
|
||||||
version: serialize_version(&buffer_version),
|
version: serialize_version(buffer_version),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,16 +34,16 @@ use gpui::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
|
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
|
||||||
point_to_lsp,
|
markdown, point_to_lsp,
|
||||||
proto::{
|
proto::{
|
||||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||||
serialize_anchor, serialize_version, split_operations,
|
serialize_anchor, serialize_version, split_operations,
|
||||||
},
|
},
|
||||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
|
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
|
||||||
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
|
||||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
|
||||||
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
|
LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
|
||||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
use lsp::{
|
use lsp::{
|
||||||
|
@ -52,7 +52,7 @@ use lsp::{
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use prettier_support::{DefaultPrettier, PrettierInstance};
|
use prettier_support::{DefaultPrettier, PrettierInstance};
|
||||||
use project_settings::{LspSettings, ProjectSettings};
|
use project_settings::{LspSettings, ProjectSettings};
|
||||||
|
@ -2947,7 +2947,7 @@ impl Project {
|
||||||
};
|
};
|
||||||
task.await;
|
task.await;
|
||||||
|
|
||||||
this.update(&mut cx, |this, mut cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let worktrees = this.worktrees.clone();
|
let worktrees = this.worktrees.clone();
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
let worktree = match worktree.upgrade() {
|
let worktree = match worktree.upgrade() {
|
||||||
|
@ -2962,7 +2962,7 @@ impl Project {
|
||||||
root_path,
|
root_path,
|
||||||
adapter.clone(),
|
adapter.clone(),
|
||||||
language.clone(),
|
language.clone(),
|
||||||
&mut cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3544,8 +3544,8 @@ impl Project {
|
||||||
if errored {
|
if errored {
|
||||||
log::warn!("test binary check failed");
|
log::warn!("test binary check failed");
|
||||||
let task = this
|
let task = this
|
||||||
.update(&mut cx, move |this, mut cx| {
|
.update(&mut cx, move |this, cx| {
|
||||||
this.reinstall_language_server(language, adapter, server_id, &mut cx)
|
this.reinstall_language_server(language, adapter, server_id, cx)
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
@ -3768,8 +3768,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(relative_glob_pattern) = relative_glob_pattern {
|
if let Some(relative_glob_pattern) = relative_glob_pattern {
|
||||||
let literal_prefix =
|
let literal_prefix = glob_literal_prefix(relative_glob_pattern);
|
||||||
glob_literal_prefix(&relative_glob_pattern);
|
|
||||||
tree.as_local_mut()
|
tree.as_local_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
|
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
|
||||||
|
@ -4231,9 +4230,9 @@ impl Project {
|
||||||
format_operation = Some(FormatOperation::Lsp(
|
format_operation = Some(FormatOperation::Lsp(
|
||||||
Self::format_via_lsp(
|
Self::format_via_lsp(
|
||||||
&project,
|
&project,
|
||||||
&buffer,
|
buffer,
|
||||||
buffer_abs_path,
|
buffer_abs_path,
|
||||||
&language_server,
|
language_server,
|
||||||
tab_size,
|
tab_size,
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
|
@ -4252,8 +4251,8 @@ impl Project {
|
||||||
format_operation = Self::format_via_external_command(
|
format_operation = Self::format_via_external_command(
|
||||||
buffer,
|
buffer,
|
||||||
buffer_abs_path,
|
buffer_abs_path,
|
||||||
&command,
|
command,
|
||||||
&arguments,
|
arguments,
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -4276,9 +4275,9 @@ impl Project {
|
||||||
format_operation = Some(FormatOperation::Lsp(
|
format_operation = Some(FormatOperation::Lsp(
|
||||||
Self::format_via_lsp(
|
Self::format_via_lsp(
|
||||||
&project,
|
&project,
|
||||||
&buffer,
|
buffer,
|
||||||
buffer_abs_path,
|
buffer_abs_path,
|
||||||
&language_server,
|
language_server,
|
||||||
tab_size,
|
tab_size,
|
||||||
&mut cx,
|
&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(
|
pub fn apply_additional_edits_for_completion(
|
||||||
&self,
|
&self,
|
||||||
buffer_handle: Model<Buffer>,
|
buffer_handle: Model<Buffer>,
|
||||||
|
@ -5683,7 +5846,7 @@ impl Project {
|
||||||
snapshot.file().map(|file| file.path().as_ref()),
|
snapshot.file().map(|file| file.path().as_ref()),
|
||||||
) {
|
) {
|
||||||
query
|
query
|
||||||
.search(&snapshot, None)
|
.search(snapshot, None)
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|range| {
|
.map(|range| {
|
||||||
|
@ -6526,7 +6689,7 @@ impl Project {
|
||||||
snapshot.repository_and_work_directory_for_path(&path)?;
|
snapshot.repository_and_work_directory_for_path(&path)?;
|
||||||
let repo = snapshot.get_local_repo(&repo)?;
|
let repo = snapshot.get_local_repo(&repo)?;
|
||||||
let relative_path = path.strip_prefix(&work_directory).ok()?;
|
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))
|
Some((buffer, base_text))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
@ -6659,7 +6822,7 @@ impl Project {
|
||||||
for (_, _, path_summary) in
|
for (_, _, path_summary) in
|
||||||
self.diagnostic_summaries(include_ignored, cx)
|
self.diagnostic_summaries(include_ignored, cx)
|
||||||
.filter(|(path, _, _)| {
|
.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)
|
include_ignored || worktree == Some(false)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
@ -6685,7 +6848,7 @@ impl Project {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.filter(move |(path, _, _)| {
|
.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)
|
include_ignored || worktree == Some(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl Project {
|
||||||
|
|
||||||
if let Some(python_settings) = &python_settings.as_option() {
|
if let Some(python_settings) = &python_settings.as_option() {
|
||||||
let activate_script_path =
|
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(
|
self.activate_python_virtual_environment(
|
||||||
activate_script_path,
|
activate_script_path,
|
||||||
&terminal_handle,
|
&terminal_handle,
|
||||||
|
|
|
@ -1437,7 +1437,7 @@ impl LocalWorktree {
|
||||||
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
|
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
|
||||||
project_id,
|
project_id,
|
||||||
worktree_id: cx.entity_id().as_u64(),
|
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));
|
return Task::ready(Err(e));
|
||||||
}
|
}
|
||||||
|
@ -2309,7 +2309,7 @@ impl LocalSnapshot {
|
||||||
impl BackgroundScannerState {
|
impl BackgroundScannerState {
|
||||||
fn should_scan_directory(&self, entry: &Entry) -> bool {
|
fn should_scan_directory(&self, entry: &Entry) -> bool {
|
||||||
(!entry.is_external && !entry.is_ignored)
|
(!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.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
|
||||||
|| self
|
|| self
|
||||||
.paths_to_scan
|
.paths_to_scan
|
||||||
|
@ -3374,7 +3374,7 @@ impl BackgroundScanner {
|
||||||
let mut is_git_related = false;
|
let mut is_git_related = false;
|
||||||
if let Some(dot_git_dir) = abs_path
|
if let Some(dot_git_dir) = abs_path
|
||||||
.ancestors()
|
.ancestors()
|
||||||
.find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
|
.find(|ancestor| ancestor.file_name() == Some(*DOT_GIT))
|
||||||
{
|
{
|
||||||
let dot_git_path = dot_git_dir
|
let dot_git_path = dot_git_dir
|
||||||
.strip_prefix(&root_canonical_path)
|
.strip_prefix(&root_canonical_path)
|
||||||
|
@ -3772,7 +3772,7 @@ impl BackgroundScanner {
|
||||||
for entry in &mut new_entries {
|
for entry in &mut new_entries {
|
||||||
state.reuse_entry_id(entry);
|
state.reuse_entry_id(entry);
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
if state.should_scan_directory(&entry) {
|
if state.should_scan_directory(entry) {
|
||||||
job_ix += 1;
|
job_ix += 1;
|
||||||
} else {
|
} else {
|
||||||
log::debug!("defer scanning directory {:?}", entry.path);
|
log::debug!("defer scanning directory {:?}", entry.path);
|
||||||
|
@ -3814,9 +3814,9 @@ impl BackgroundScanner {
|
||||||
abs_paths
|
abs_paths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|abs_path| async move {
|
.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 {
|
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)))
|
anyhow::Ok(Some((metadata, canonical_path)))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -3864,7 +3864,7 @@ impl BackgroundScanner {
|
||||||
fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
|
fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
|
||||||
|
|
||||||
if !is_dir && !fs_entry.is_ignored {
|
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) {
|
if let Ok(repo_path) = path.strip_prefix(work_dir.0) {
|
||||||
let repo_path = RepoPath(repo_path.into());
|
let repo_path = RepoPath(repo_path.into());
|
||||||
let repo = repo.repo_ptr.lock();
|
let repo = repo.repo_ptr.lock();
|
||||||
|
@ -3884,7 +3884,7 @@ impl BackgroundScanner {
|
||||||
state.insert_entry(fs_entry, self.fs.as_ref());
|
state.insert_entry(fs_entry, self.fs.as_ref());
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
self.remove_repo_path(&path, &mut state.snapshot);
|
self.remove_repo_path(path, &mut state.snapshot);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// TODO - create a special 'error' entry in the entries tree to mark this
|
// 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" }
|
theme = { path = "../theme" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
ui = { path = "../ui" }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
|
|
|
@ -6,6 +6,7 @@ use gpui::{
|
||||||
use language::{HighlightId, Language, LanguageRegistry};
|
use language::{HighlightId, Language, LanguageRegistry};
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
use ui::LinkPreview;
|
||||||
use util::RangeExt;
|
use util::RangeExt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -64,7 +65,7 @@ impl RichText {
|
||||||
},
|
},
|
||||||
Highlight::Id(id) => HighlightStyle {
|
Highlight::Id(id) => HighlightStyle {
|
||||||
background_color: Some(code_background),
|
background_color: Some(code_background),
|
||||||
..id.style(&theme.syntax()).unwrap_or_default()
|
..id.style(theme.syntax()).unwrap_or_default()
|
||||||
},
|
},
|
||||||
Highlight::Highlight(highlight) => *highlight,
|
Highlight::Highlight(highlight) => *highlight,
|
||||||
Highlight::Mention => HighlightStyle {
|
Highlight::Mention => HighlightStyle {
|
||||||
|
@ -84,6 +85,18 @@ impl RichText {
|
||||||
let link_urls = self.link_urls.clone();
|
let link_urls = self.link_urls.clone();
|
||||||
move |ix, cx| cx.open_url(&link_urls[ix])
|
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()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +120,7 @@ pub fn render_markdown_mut(
|
||||||
let mut list_stack = Vec::new();
|
let mut list_stack = Vec::new();
|
||||||
|
|
||||||
let options = Options::all();
|
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();
|
let prev_len = text.len();
|
||||||
match event {
|
match event {
|
||||||
Event::Text(t) => {
|
Event::Text(t) => {
|
||||||
|
@ -237,7 +250,7 @@ pub fn render_markdown_mut(
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::HardBreak => text.push('\n'),
|
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) =
|
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())));
|
paths_stack.push(Some((*root_id, path.as_ref())));
|
||||||
user_settings_stack.push(local_settings);
|
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() {
|
if let Some(new_object) = new_value.as_object_mut() {
|
||||||
new_object.retain(|_, v| !v.is_null());
|
new_object.retain(|_, v| !v.is_null());
|
||||||
}
|
}
|
||||||
let (range, replacement) =
|
let (range, replacement) = replace_value_in_json_text(text, key_path, tab_size, &new_value);
|
||||||
replace_value_in_json_text(text, &key_path, tab_size, &new_value);
|
|
||||||
text.replace_range(range.clone(), &replacement);
|
text.replace_range(range.clone(), &replacement);
|
||||||
edits.push((range, replacement));
|
edits.push((range, replacement));
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,8 @@ impl StoryContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for StoryContainer {
|
impl ParentElement for StoryContainer {
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||||
&mut self.children
|
self.children.extend(elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +372,7 @@ impl RenderOnce for StorySection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParentElement for StorySection {
|
impl ParentElement for StorySection {
|
||||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||||
&mut self.children
|
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