From 7cd416c63e6c8fd9d71bd4c343085266b11d9a39 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 25 Aug 2023 21:42:18 -0600 Subject: [PATCH 001/141] Always log panics --- crates/zed/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index da726eef65..22a22ab0cf 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -495,11 +495,11 @@ fn init_panic_hook(app: &App, installation_id: Option) { installation_id: installation_id.clone(), }; - if is_pty { - if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() { - eprintln!("{}", panic_data_json); - } - } else { + if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() { + log::error!("{}", panic_data_json); + } + + if !is_pty { if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() { let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp)); From a63eccf18839ecbfd35f45bd2da87b0c73a4cb2d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 4 Oct 2023 22:46:28 -0600 Subject: [PATCH 002/141] Add url schemes to Zed --- crates/util/src/channel.rs | 26 ++++++++++++++++++++++++++ crates/zed/Cargo.toml | 3 +++ 2 files changed, 29 insertions(+) diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 274fd576a0..89d42ffba6 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,6 +1,7 @@ use std::env; use lazy_static::lazy_static; +use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -15,6 +16,23 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; + + static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "zed-dev:/", + "preview" => "zed-preview:/", + "stable" => "zed:/", + // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); + static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + "dev" => "http://localhost:3000/dev/", + "preview" => "https://zed.dev/preview/", + "stable" => "https://zed.dev/", + // NOTE: this must be kept in sync with https://zed.dev. + _ => unreachable!(), + }) + .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -41,4 +59,12 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static Url { + &URL_SCHEME + } + + pub fn link_prefix(&self) -> &'static Url { + &LINK_PREFIX + } } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d4ac972a5d..7eb14559be 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -162,6 +162,7 @@ identifier = "dev.zed.Zed-Dev" name = "Zed Dev" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-dev"] [package.metadata.bundle-preview] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] @@ -169,6 +170,7 @@ identifier = "dev.zed.Zed-Preview" name = "Zed Preview" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed-preview"] [package.metadata.bundle-stable] @@ -177,3 +179,4 @@ identifier = "dev.zed.Zed" name = "Zed" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] +osx_url_schemes = ["zed"] From b258ee5f77f9e84e2d0f7d7aad802dca9076b321 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 11:04:23 -0600 Subject: [PATCH 003/141] Fix ./script/bundle -l --- crates/zed/Cargo.toml | 2 +- script/bundle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7eb14559be..3c93462d4b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle-dev] +[package.metadata.bundle] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/script/bundle b/script/bundle index 49da1072ce..4882189c22 100755 --- a/script/bundle +++ b/script/bundle @@ -16,7 +16,7 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode and print the app bundle's path. + -d Compile in debug mode (doesn't currently work without -l) -l Compile for local architecture only and copy bundle to /Applications. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. @@ -92,7 +92,7 @@ sed \ Cargo.toml if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) + app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi From 13192fa03ca4548bd29abed93597c6ac1274340b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 14:23:14 -0600 Subject: [PATCH 004/141] Code to allow opening zed:/channel/1234 Refactored a bit how url arguments are handled to avoid adding too much extra complexity to main. --- crates/cli/src/main.rs | 1 + crates/collab_ui/src/collab_panel.rs | 83 ++++++---------- crates/util/src/channel.rs | 17 +--- crates/workspace/src/workspace.rs | 82 ++++++++++++++++ crates/zed/src/main.rs | 137 +++++++++++---------------- crates/zed/src/open_url.rs | 101 ++++++++++++++++++++ 6 files changed, 272 insertions(+), 149 deletions(-) create mode 100644 crates/zed/src/open_url.rs diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2f742814a8..69cfb7102b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -182,6 +182,7 @@ impl Bundle { kCFStringEncodingUTF8, ptr::null(), )); + // equivalent to: open zed-cli:... -a /Applications/Zed\ Preview.app let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); LSOpenFromURLSpec( &LSLaunchURLSpec { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 951c8bf70c..3d66e8450a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1969,18 +1969,21 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new(channel.name.clone(), style.text.clone()) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new( + channel.name.clone().to_owned() + channel_id.to_string().as_str(), + style.text.clone(), + ) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = @@ -3187,49 +3190,19 @@ impl CollabPanel { } fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { - let workspace = self.workspace.clone(); - let window = cx.window(); - let active_call = ActiveCall::global(cx); - cx.spawn(|_, mut cx| async move { - if active_call.read_with(&mut cx, |active_call, cx| { - if let Some(room) = active_call.room() { - let room = room.read(cx); - room.is_sharing_project() && room.remote_participants().len() > 0 - } else { - false - } - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); - - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return anyhow::Ok(()); - } - } - } - - let room = active_call - .update(&mut cx, |call, cx| call.join_channel(channel_id, cx)) - .await?; - - let task = room.update(&mut cx, |room, cx| { - let workspace = workspace.upgrade(cx)?; - let (project, host) = room.most_active_project()?; - let app_state = workspace.read(cx).app_state().clone(); - Some(workspace::join_remote_project(project, host, app_state, cx)) - }); - if let Some(task) = task { - task.await?; - } - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + let Some(workspace) = self.workspace.upgrade(cx) else { + return; + }; + let Some(handle) = cx.window().downcast::() else { + return; + }; + workspace::join_channel( + channel_id, + workspace.read(cx).app_state().clone(), + Some(handle), + cx, + ) + .detach_and_log_err(cx) } fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext) { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 89d42ffba6..761b17e6af 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -17,15 +17,14 @@ lazy_static! { _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - static ref URL_SCHEME: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { "dev" => "zed-dev:/", "preview" => "zed-preview:/", "stable" => "zed:/", - // NOTE: this must be kept in sync with ./script/bundle and https://zed.dev. + // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. _ => unreachable!(), - }) - .unwrap(); - static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { + }.to_string(); + pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { "dev" => "http://localhost:3000/dev/", "preview" => "https://zed.dev/preview/", "stable" => "https://zed.dev/", @@ -59,12 +58,4 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } - - pub fn url_scheme(&self) -> &'static Url { - &URL_SCHEME - } - - pub fn link_prefix(&self) -> &'static Url { - &LINK_PREFIX - } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f7bb409229..5ec847b28b 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4154,6 +4154,88 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| async move { + let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { + let Some(room) = active_call.room().map( |room| room.read(cx) ) else { + return false + }; + + room.is_sharing_project() && room.remote_participants().len() > 0 && + room.channel_id() != Some(channel_id) + }); + + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(&mut cx, |cx| { + cx.window() + }) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + &mut cx, + ); + + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(()); + } + } + } + } + } + + let room = active_call.update(&mut cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }).await?; + + let task = room.update(&mut cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)) + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(()); + } + + if requesting_window.is_some() { + return anyhow::Ok(()); + } + + // find an existing workspace to focus and show call controls + for window in cx.windows() { + let found = window.update(&mut cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + + if found.unwrap_or(false) { + return anyhow::Ok(()) + } + } + + // no open workspaces + cx.update(|cx| { + Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) + }).await; + + return anyhow::Ok(()); + }) +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index d6f3be2b46..c491d406af 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -45,7 +45,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{ReleaseChannel, URL_SCHEME_PREFIX}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -61,6 +61,10 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; +use crate::open_url::{OpenListener, OpenRequest}; + +mod open_url; + fn main() { let http = http::client(); init_paths(); @@ -92,29 +96,20 @@ fn main() { }) }; - let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); - let cli_connections_tx = Arc::new(cli_connections_tx); - let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); - let open_paths_tx = Arc::new(open_paths_tx); - let urls_callback_triggered = Arc::new(AtomicBool::new(false)); - - let callback_cli_connections_tx = Arc::clone(&cli_connections_tx); - let callback_open_paths_tx = Arc::clone(&open_paths_tx); - let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered); - app.on_open_urls(move |urls, _| { - callback_urls_callback_triggered.store(true, Ordering::Release); - open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx); - }) - .on_reopen(move |cx| { - if cx.has_global::>() { - if let Some(app_state) = cx.global::>().upgrade() { - workspace::open_new(&app_state, cx, |workspace, cx| { - Editor::new_file(workspace, &Default::default(), cx) - }) - .detach(); + let (listener, mut open_rx) = OpenListener::new(); + let listener = Arc::new(listener); + let callback_listener = listener.clone(); + app.on_open_urls(move |urls, _| callback_listener.open_urls(urls)) + .on_reopen(move |cx| { + if cx.has_global::>() { + if let Some(app_state) = cx.global::>().upgrade() { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } } - } - }); + }); app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); @@ -226,41 +221,52 @@ fn main() { // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() - && !urls_callback_triggered.load(Ordering::Acquire) + && !listener.triggered.load(Ordering::Acquire) { - open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx) + listener.open_urls(collect_url_args()) } - if let Ok(Some(connection)) = cli_connections_rx.try_next() { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } else if let Ok(Some(paths)) = open_paths_rx.try_next() { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } else { - cx.spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach() - } - - cx.spawn(|cx| { - let app_state = app_state.clone(); - async move { - while let Some(connection) = cli_connections_rx.next().await { - handle_cli_connection(connection, app_state.clone(), cx.clone()).await; - } + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); } - }) - .detach(); + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach(), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } cx.spawn(|mut cx| { let app_state = app_state.clone(); async move { - while let Some(paths) = open_paths_rx.next().await { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); + } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| { + handle_cli_connection(connection, app_state.clone(), cx) + }) + .detach(); + } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), + } } } }) @@ -297,37 +303,6 @@ async fn installation_id() -> Result { } } -fn open_urls( - urls: Vec, - cli_connections_tx: &mpsc::UnboundedSender<( - mpsc::Receiver, - IpcSender, - )>, - open_paths_tx: &mpsc::UnboundedSender>, -) { - if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - if let Some(cli_connection) = connect_to_cli(server_name).log_err() { - cli_connections_tx - .unbounded_send(cli_connection) - .map_err(|_| anyhow!("no listener for cli connections")) - .log_err(); - }; - } else { - let paths: Vec<_> = urls - .iter() - .flat_map(|url| url.strip_prefix("file://")) - .map(|url| { - let decoded = urlencoding::decode_binary(url.as_bytes()); - PathBuf::from(OsStr::from_bytes(decoded.as_ref())) - }) - .collect(); - open_paths_tx - .unbounded_send(paths) - .map_err(|_| anyhow!("no listener for open urls requests")) - .log_err(); - } -} - async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace::last_opened_workspace_paths().await { cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx)) diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs new file mode 100644 index 0000000000..f421633d5b --- /dev/null +++ b/crates/zed/src/open_url.rs @@ -0,0 +1,101 @@ +use anyhow::anyhow; +use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use futures::channel::mpsc; +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use std::ffi::OsStr; +use std::os::unix::prelude::OsStrExt; +use std::sync::atomic::Ordering; +use std::{path::PathBuf, sync::atomic::AtomicBool}; +use util::channel::URL_SCHEME_PREFIX; +use util::ResultExt; + +use crate::{connect_to_cli, handle_cli_connection}; + +pub enum OpenRequest { + Paths { + paths: Vec, + }, + CliConnection { + connection: (mpsc::Receiver, IpcSender), + }, + JoinChannel { + channel_id: u64, + }, +} + +pub struct OpenListener { + tx: UnboundedSender, + pub triggered: AtomicBool, +} + +impl OpenListener { + pub fn new() -> (Self, UnboundedReceiver) { + let (tx, rx) = mpsc::unbounded(); + ( + OpenListener { + tx, + triggered: AtomicBool::new(false), + }, + rx, + ) + } + + pub fn open_urls(&self, urls: Vec) { + self.triggered.store(true, Ordering::Release); + dbg!(&urls); + let request = if let Some(server_name) = + urls.first().and_then(|url| url.strip_prefix("zed-cli://")) + { + self.handle_cli_connection(server_name) + } else if let Some(request_path) = urls + .first() + .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) + { + self.handle_zed_url_scheme(request_path) + } else { + self.handle_file_urls(urls) + }; + + if let Some(request) = request { + self.tx + .unbounded_send(request) + .map_err(|_| anyhow!("no listener for open requests")) + .log_err(); + } + } + + fn handle_cli_connection(&self, server_name: &str) -> Option { + if let Some(connection) = connect_to_cli(server_name).log_err() { + return Some(OpenRequest::CliConnection { connection }); + } + + None + } + + fn handle_zed_url_scheme(&self, request_path: &str) -> Option { + let mut parts = request_path.split("/"); + if parts.next() == Some("channel") { + if let Some(slug) = parts.next() { + if let Some(id_str) = slug.split("-").last() { + if let Ok(channel_id) = id_str.parse::() { + return Some(OpenRequest::JoinChannel { channel_id }); + } + } + } + } + None + } + + fn handle_file_urls(&self, urls: Vec) -> Option { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|url| { + let decoded = urlencoding::decode_binary(url.as_bytes()); + PathBuf::from(OsStr::from_bytes(decoded.as_ref())) + }) + .collect(); + + Some(OpenRequest::Paths { paths }) + } +} From 31062d424f385c34ccc3496e74dbd0d57978cf90 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 5 Oct 2023 16:41:08 -0700 Subject: [PATCH 005/141] make bundle script incremental when using debug or local builds --- crates/zed/resources/zed.entitlements | 10 ++-------- crates/zed/src/main.rs | 6 ++---- crates/zed/src/open_url.rs | 2 +- script/bundle | 27 +++++++++++++++++++-------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index f40a8a253a..dca0ff6766 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -10,14 +10,8 @@ com.apple.security.device.camera - com.apple.security.personal-information.addressbook - - com.apple.security.personal-information.calendars - - com.apple.security.personal-information.location - - com.apple.security.personal-information.photos-library - + com.apple.security.keychain-access-groups + MQ55VZLNZQ.dev.zed.Shared diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c491d406af..9d0451ecfa 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -32,12 +32,10 @@ use std::{ ffi::OsStr, fs::OpenOptions, io::{IsTerminal, Write as _}, - os::unix::prelude::OsStrExt, panic, path::{Path, PathBuf}, - str, sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, + atomic::{AtomicU32, Ordering}, Arc, Weak, }, thread, @@ -45,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::{ReleaseChannel, URL_SCHEME_PREFIX}, + channel::ReleaseChannel, http::{self, HttpClient}, paths::PathLikeWithPosition, }; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index f421633d5b..1c741a02c8 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -9,7 +9,7 @@ use std::{path::PathBuf, sync::atomic::AtomicBool}; use util::channel::URL_SCHEME_PREFIX; use util::ResultExt; -use crate::{connect_to_cli, handle_cli_connection}; +use crate::connect_to_cli; pub enum OpenRequest { Paths { diff --git a/script/bundle b/script/bundle index 4882189c22..94741d290f 100755 --- a/script/bundle +++ b/script/bundle @@ -5,6 +5,7 @@ set -e build_flag="--release" target_dir="release" open_result=false +local_arch=false local_only=false overwrite_local_app=false bundle_name="" @@ -16,8 +17,8 @@ Usage: ${0##*/} [options] [bundle_name] Build the application bundle. Options: - -d Compile in debug mode (doesn't currently work without -l) - -l Compile for local architecture only and copy bundle to /Applications. + -d Compile in debug mode + -l Compile for local architecture and copy bundle to /Applications, implies -d. -o Open the resulting DMG or the app itself in local mode. -f Overwrite the local app bundle if it exists. -h Display this help and exit. @@ -32,10 +33,20 @@ do case "${flag}" in o) open_result=true;; d) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true build_flag=""; + local_arch=true + target_dir="debug" + ;; + l) + export CARGO_INCREMENTAL=true + export CARGO_BUNDLE_SKIP_BUILD=true + build_flag="" + local_arch=true + local_only=true target_dir="debug" ;; - l) local_only=true;; f) overwrite_local_app=true;; h) help_info @@ -67,7 +78,7 @@ version_info=$(rustc --version --verbose) host_line=$(echo "$version_info" | grep host) local_target_triple=${host_line#*: } -if [ "$local_only" = true ]; then +if [ "$local_arch" = true ]; then echo "Building for local target only." cargo build ${build_flag} --package zed cargo build ${build_flag} --package cli @@ -91,8 +102,8 @@ sed \ "s/package.metadata.bundle-${channel}/package.metadata.bundle/" \ Cargo.toml -if [ "$local_only" = true ]; then - app_path=$(cargo bundle ${build_flag} --target "$local_target_triple" --select-workspace-root | xargs) +if [ "$local_arch" = true ]; then + app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs) else app_path=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs) fi @@ -101,7 +112,7 @@ mv Cargo.toml.backup Cargo.toml popd echo "Bundled ${app_path}" -if [ "$local_only" = false ]; then +if [ "$local_arch" = false ]; then echo "Creating fat binaries" lipo \ -create \ @@ -136,7 +147,7 @@ else codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v fi -if [ "$target_dir" = "debug" ]; then +if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then if [ "$open_result" = true ]; then open "$app_path" else From 2d99b327fc2e7f094692ffd241193180d66a8818 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 5 Oct 2023 19:26:22 -0600 Subject: [PATCH 006/141] Don't wrap on paragraphs For zed-industries/community#2116 --- .cargo/config.toml | 2 +- crates/editor/src/editor_tests.rs | 45 +++++++------------ crates/editor/src/movement.rs | 4 +- crates/vim/src/test.rs | 25 +++++++++++ .../test_data/test_paragraphs_dont_wrap.json | 8 ++++ 5 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 crates/vim/test_data/test_paragraphs_dont_wrap.json diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be08..e22bdb0f2c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dee27e0121..dc723c7012 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1333,7 +1333,7 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); cx.assert_editor_state( - &r#"ˇone + &r#"one two three @@ -1344,9 +1344,22 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon .unindent(), ); - cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx)); + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); cx.assert_editor_state( - &r#"ˇone + &r#"one + two + + three + four + five + ˇ + six"# + .unindent(), + ); + + cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); + cx.assert_editor_state( + &r#"one two ˇ three @@ -1366,32 +1379,6 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon four five - sixˇ"# - .unindent(), - ); - - cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); - cx.assert_editor_state( - &r#"one - two - - three - four - five - ˇ - sixˇ"# - .unindent(), - ); - - cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx)); - cx.assert_editor_state( - &r#"one - two - ˇ - three - four - five - ˇ six"# .unindent(), ); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 974af4bc24..245c2d9977 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -234,7 +234,7 @@ pub fn start_of_paragraph( ) -> DisplayPoint { let point = display_point.to_point(map); if point.row == 0 { - return map.max_point(); + return DisplayPoint::zero(); } let mut found_non_blank_line = false; @@ -261,7 +261,7 @@ pub fn end_of_paragraph( ) -> DisplayPoint { let point = display_point.to_point(map); if point.row == map.max_buffer_row() { - return DisplayPoint::zero(); + return map.max_point(); } let mut found_non_blank_line = false; diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 52dcb54ce2..34b9e38768 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -652,3 +652,28 @@ async fn test_selection_goal(cx: &mut gpui::TestAppContext) { Lorem Ipsum"}) .await; } + +#[gpui::test] +async fn test_paragraphs_dont_wrap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + one + ˇ + two"}) + .await; + + cx.simulate_shared_keystrokes(["}", "}"]).await; + cx.assert_shared_state(indoc! {" + one + + twˇo"}) + .await; + + cx.simulate_shared_keystrokes(["{", "{", "{"]).await; + cx.assert_shared_state(indoc! {" + ˇone + + two"}) + .await; +} diff --git a/crates/vim/test_data/test_paragraphs_dont_wrap.json b/crates/vim/test_data/test_paragraphs_dont_wrap.json new file mode 100644 index 0000000000..9e729651be --- /dev/null +++ b/crates/vim/test_data/test_paragraphs_dont_wrap.json @@ -0,0 +1,8 @@ +{"Put":{"state":"one\nˇ\ntwo"}} +{"Key":"}"} +{"Key":"}"} +{"Get":{"state":"one\n\ntwˇo","mode":"Normal"}} +{"Key":"{"} +{"Key":"{"} +{"Key":"{"} +{"Get":{"state":"ˇone\n\ntwo","mode":"Normal"}} From 2c7e37e9fff48031618f956374848c952a7364a5 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 6 Oct 2023 12:32:20 -0400 Subject: [PATCH 007/141] Add session id to panic events --- crates/client/src/telemetry.rs | 45 ++++++++++++++++++---------------- crates/zed/src/main.rs | 9 ++++--- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 0f753679e1..70878bf2e4 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -8,7 +8,6 @@ use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; use tempfile::NamedTempFile; use util::http::HttpClient; use util::{channel::ReleaseChannel, TryFutureExt}; -use uuid::Uuid; pub struct Telemetry { http_client: Arc, @@ -20,7 +19,7 @@ pub struct Telemetry { struct TelemetryState { metrics_id: Option>, // Per logged-in user installation_id: Option>, // Per app installation (different for dev, preview, and stable) - session_id: String, // Per app launch + session_id: Option>, // Per app launch app_version: Option>, release_channel: Option<&'static str>, os_name: &'static str, @@ -43,7 +42,7 @@ lazy_static! { struct ClickhouseEventRequestBody { token: &'static str, installation_id: Option>, - session_id: String, + session_id: Option>, is_staff: Option, app_version: Option>, os_name: &'static str, @@ -134,7 +133,7 @@ impl Telemetry { release_channel, installation_id: None, metrics_id: None, - session_id: Uuid::new_v4().to_string(), + session_id: None, clickhouse_events_queue: Default::default(), flush_clickhouse_events_task: Default::default(), log_file: None, @@ -149,9 +148,15 @@ impl Telemetry { Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) } - pub fn start(self: &Arc, installation_id: Option, cx: &mut AppContext) { + pub fn start( + self: &Arc, + installation_id: Option, + session_id: String, + cx: &mut AppContext, + ) { let mut state = self.state.lock(); state.installation_id = installation_id.map(|id| id.into()); + state.session_id = Some(session_id.into()); let has_clickhouse_events = !state.clickhouse_events_queue.is_empty(); drop(state); @@ -283,23 +288,21 @@ impl Telemetry { { let state = this.state.lock(); - json_bytes.clear(); - serde_json::to_writer( - &mut json_bytes, - &ClickhouseEventRequestBody { - token: ZED_SECRET_CLIENT_TOKEN, - installation_id: state.installation_id.clone(), - session_id: state.session_id.clone(), - is_staff: state.is_staff.clone(), - app_version: state.app_version.clone(), - os_name: state.os_name, - os_version: state.os_version.clone(), - architecture: state.architecture, + let request_body = ClickhouseEventRequestBody { + token: ZED_SECRET_CLIENT_TOKEN, + installation_id: state.installation_id.clone(), + session_id: state.session_id.clone(), + is_staff: state.is_staff.clone(), + app_version: state.app_version.clone(), + os_name: state.os_name, + os_version: state.os_version.clone(), + architecture: state.architecture, - release_channel: state.release_channel, - events, - }, - )?; + release_channel: state.release_channel, + events, + }; + json_bytes.clear(); + serde_json::to_writer(&mut json_bytes, &request_body)?; } this.http_client diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index d6f3be2b46..704eef464c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -74,7 +74,8 @@ fn main() { let mut app = gpui::App::new(Assets).unwrap(); let installation_id = app.background().block(installation_id()).ok(); - init_panic_hook(&app, installation_id.clone()); + let session_id = Uuid::new_v4().to_string(); + init_panic_hook(&app, installation_id.clone(), session_id.clone()); load_embedded_fonts(&app); @@ -177,7 +178,7 @@ fn main() { }) .detach(); - client.telemetry().start(installation_id, cx); + client.telemetry().start(installation_id, session_id, cx); let app_state = Arc::new(AppState { languages, @@ -402,6 +403,7 @@ struct Panic { panicked_on: u128, #[serde(skip_serializing_if = "Option::is_none")] installation_id: Option, + session_id: String, } #[derive(Serialize)] @@ -412,7 +414,7 @@ struct PanicRequest { static PANIC_COUNT: AtomicU32 = AtomicU32::new(0); -fn init_panic_hook(app: &App, installation_id: Option) { +fn init_panic_hook(app: &App, installation_id: Option, session_id: String) { let is_pty = stdout_is_a_pty(); let platform = app.platform(); @@ -490,6 +492,7 @@ fn init_panic_hook(app: &App, installation_id: Option) { .as_millis(), backtrace, installation_id: installation_id.clone(), + session_id: session_id.clone(), }; if is_pty { From 456baaa112e8eb6b13fd031abd6818e61b97e812 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 6 Oct 2023 13:18:56 -0400 Subject: [PATCH 008/141] Mainline GPUI2 UI work (#3099) This PR mainlines the current state of new GPUI2-based UI from the `gpui2-ui` branch. Included in this is a performance improvement to make use of the `TextLayoutCache` when calling `layout` for `Text` elements. Release Notes: - N/A --------- Co-authored-by: Nate Butler Co-authored-by: Antonio Scandurra --- crates/gpui/src/app/window.rs | 6 +- crates/gpui2/src/elements/text.rs | 16 ++--- crates/storybook/src/stories/components.rs | 4 ++ .../stories/components/language_selector.rs | 16 +++++ .../src/stories/components/multi_buffer.rs | 24 +++++++ .../src/stories/components/recent_projects.rs | 16 +++++ .../src/stories/components/theme_selector.rs | 16 +++++ crates/storybook/src/story_selector.rs | 14 ++++ crates/ui/src/components.rs | 10 +++ crates/ui/src/components/language_selector.rs | 36 ++++++++++ crates/ui/src/components/multi_buffer.rs | 42 ++++++++++++ crates/ui/src/components/palette.rs | 20 ++++-- crates/ui/src/components/recent_projects.rs | 32 +++++++++ crates/ui/src/components/theme_selector.rs | 37 +++++++++++ crates/ui/src/components/toast.rs | 66 +++++++++++++++++++ crates/ui/src/components/workspace.rs | 13 ++++ crates/ui/src/elements/icon.rs | 2 + crates/ui/src/prelude.rs | 20 ++++++ 18 files changed, 371 insertions(+), 19 deletions(-) create mode 100644 crates/storybook/src/stories/components/language_selector.rs create mode 100644 crates/storybook/src/stories/components/multi_buffer.rs create mode 100644 crates/storybook/src/stories/components/recent_projects.rs create mode 100644 crates/storybook/src/stories/components/theme_selector.rs create mode 100644 crates/ui/src/components/language_selector.rs create mode 100644 crates/ui/src/components/multi_buffer.rs create mode 100644 crates/ui/src/components/recent_projects.rs create mode 100644 crates/ui/src/components/theme_selector.rs create mode 100644 crates/ui/src/components/toast.rs diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 4eca6f3a30..7ba1e85100 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -71,7 +71,7 @@ pub struct Window { pub(crate) hovered_region_ids: Vec, pub(crate) clicked_region_ids: Vec, pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>, - text_layout_cache: TextLayoutCache, + text_layout_cache: Arc, refreshing: bool, } @@ -107,7 +107,7 @@ impl Window { cursor_regions: Default::default(), mouse_regions: Default::default(), event_handlers: Default::default(), - text_layout_cache: TextLayoutCache::new(cx.font_system.clone()), + text_layout_cache: Arc::new(TextLayoutCache::new(cx.font_system.clone())), last_mouse_moved_event: None, last_mouse_position: Vector2F::zero(), pressed_buttons: Default::default(), @@ -303,7 +303,7 @@ impl<'a> WindowContext<'a> { self.window.refreshing } - pub fn text_layout_cache(&self) -> &TextLayoutCache { + pub fn text_layout_cache(&self) -> &Arc { &self.window.text_layout_cache } diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 6f89375df0..323b3d9f89 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::Result; use gpui::{ geometry::{vector::Vector2F, Size}, - text_layout::LineLayout, + text_layout::Line, LayoutId, }; use parking_lot::Mutex; @@ -32,7 +32,7 @@ impl Element for Text { _view: &mut V, cx: &mut ViewContext, ) -> Result<(LayoutId, Self::PaintState)> { - let fonts = cx.platform().fonts(); + let layout_cache = cx.text_layout_cache().clone(); let text_style = cx.text_style(); let line_height = cx.font_cache().line_height(text_style.font_size); let text = self.text.clone(); @@ -41,14 +41,14 @@ impl Element for Text { let layout_id = cx.add_measured_layout_node(Default::default(), { let paint_state = paint_state.clone(); move |_params| { - let line_layout = fonts.layout_line( + let line_layout = layout_cache.layout_str( text.as_ref(), text_style.font_size, &[(text.len(), text_style.to_run())], ); let size = Size { - width: line_layout.width, + width: line_layout.width(), height: line_height, }; @@ -85,13 +85,9 @@ impl Element for Text { line_height = paint_state.line_height; } - let text_style = cx.text_style(); - let line = - gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]); - // TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder. let visible_bounds = bounds; - line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx); + line_layout.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx); } } @@ -104,6 +100,6 @@ impl IntoElement for Text { } pub struct TextLayout { - line_layout: Arc, + line_layout: Arc, line_height: f32, } diff --git a/crates/storybook/src/stories/components.rs b/crates/storybook/src/stories/components.rs index 345fcfa309..85d5ce088f 100644 --- a/crates/storybook/src/stories/components.rs +++ b/crates/storybook/src/stories/components.rs @@ -6,13 +6,17 @@ pub mod collab_panel; pub mod context_menu; pub mod facepile; pub mod keybinding; +pub mod language_selector; +pub mod multi_buffer; pub mod palette; pub mod panel; pub mod project_panel; +pub mod recent_projects; pub mod status_bar; pub mod tab; pub mod tab_bar; pub mod terminal; +pub mod theme_selector; pub mod title_bar; pub mod toolbar; pub mod traffic_lights; diff --git a/crates/storybook/src/stories/components/language_selector.rs b/crates/storybook/src/stories/components/language_selector.rs new file mode 100644 index 0000000000..c6dbd13d3f --- /dev/null +++ b/crates/storybook/src/stories/components/language_selector.rs @@ -0,0 +1,16 @@ +use ui::prelude::*; +use ui::LanguageSelector; + +use crate::story::Story; + +#[derive(Element, Default)] +pub struct LanguageSelectorStory {} + +impl LanguageSelectorStory { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + Story::container(cx) + .child(Story::title_for::<_, LanguageSelector>(cx)) + .child(Story::label(cx, "Default")) + .child(LanguageSelector::new()) + } +} diff --git a/crates/storybook/src/stories/components/multi_buffer.rs b/crates/storybook/src/stories/components/multi_buffer.rs new file mode 100644 index 0000000000..cd760c54dc --- /dev/null +++ b/crates/storybook/src/stories/components/multi_buffer.rs @@ -0,0 +1,24 @@ +use ui::prelude::*; +use ui::{hello_world_rust_buffer_example, MultiBuffer}; + +use crate::story::Story; + +#[derive(Element, Default)] +pub struct MultiBufferStory {} + +impl MultiBufferStory { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + Story::container(cx) + .child(Story::title_for::<_, MultiBuffer>(cx)) + .child(Story::label(cx, "Default")) + .child(MultiBuffer::new(vec![ + hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(&theme), + ])) + } +} diff --git a/crates/storybook/src/stories/components/recent_projects.rs b/crates/storybook/src/stories/components/recent_projects.rs new file mode 100644 index 0000000000..f924654695 --- /dev/null +++ b/crates/storybook/src/stories/components/recent_projects.rs @@ -0,0 +1,16 @@ +use ui::prelude::*; +use ui::RecentProjects; + +use crate::story::Story; + +#[derive(Element, Default)] +pub struct RecentProjectsStory {} + +impl RecentProjectsStory { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + Story::container(cx) + .child(Story::title_for::<_, RecentProjects>(cx)) + .child(Story::label(cx, "Default")) + .child(RecentProjects::new()) + } +} diff --git a/crates/storybook/src/stories/components/theme_selector.rs b/crates/storybook/src/stories/components/theme_selector.rs new file mode 100644 index 0000000000..43e2a704e7 --- /dev/null +++ b/crates/storybook/src/stories/components/theme_selector.rs @@ -0,0 +1,16 @@ +use ui::prelude::*; +use ui::ThemeSelector; + +use crate::story::Story; + +#[derive(Element, Default)] +pub struct ThemeSelectorStory {} + +impl ThemeSelectorStory { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + Story::container(cx) + .child(Story::title_for::<_, ThemeSelector>(cx)) + .child(Story::label(cx, "Default")) + .child(ThemeSelector::new()) + } +} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index acc965aa0a..6b0a9ac78d 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -42,13 +42,17 @@ pub enum ComponentStory { CollabPanel, Facepile, Keybinding, + LanguageSelector, + MultiBuffer, Palette, Panel, ProjectPanel, + RecentProjects, StatusBar, Tab, TabBar, Terminal, + ThemeSelector, TitleBar, Toolbar, TrafficLights, @@ -69,15 +73,25 @@ impl ComponentStory { Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(), Self::Facepile => components::facepile::FacepileStory::default().into_any(), Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(), + Self::LanguageSelector => { + components::language_selector::LanguageSelectorStory::default().into_any() + } + Self::MultiBuffer => components::multi_buffer::MultiBufferStory::default().into_any(), Self::Palette => components::palette::PaletteStory::default().into_any(), Self::Panel => components::panel::PanelStory::default().into_any(), Self::ProjectPanel => { components::project_panel::ProjectPanelStory::default().into_any() } + Self::RecentProjects => { + components::recent_projects::RecentProjectsStory::default().into_any() + } Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(), Self::Tab => components::tab::TabStory::default().into_any(), Self::TabBar => components::tab_bar::TabBarStory::default().into_any(), Self::Terminal => components::terminal::TerminalStory::default().into_any(), + Self::ThemeSelector => { + components::theme_selector::ThemeSelectorStory::default().into_any() + } Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(), Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(), Self::TrafficLights => { diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 0af13040f7..65b0218565 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -9,17 +9,22 @@ mod editor_pane; mod facepile; mod icon_button; mod keybinding; +mod language_selector; mod list; +mod multi_buffer; mod palette; mod panel; mod panes; mod player_stack; mod project_panel; +mod recent_projects; mod status_bar; mod tab; mod tab_bar; mod terminal; +mod theme_selector; mod title_bar; +mod toast; mod toolbar; mod traffic_lights; mod workspace; @@ -35,17 +40,22 @@ pub use editor_pane::*; pub use facepile::*; pub use icon_button::*; pub use keybinding::*; +pub use language_selector::*; pub use list::*; +pub use multi_buffer::*; pub use palette::*; pub use panel::*; pub use panes::*; pub use player_stack::*; pub use project_panel::*; +pub use recent_projects::*; pub use status_bar::*; pub use tab::*; pub use tab_bar::*; pub use terminal::*; +pub use theme_selector::*; pub use title_bar::*; +pub use toast::*; pub use toolbar::*; pub use traffic_lights::*; pub use workspace::*; diff --git a/crates/ui/src/components/language_selector.rs b/crates/ui/src/components/language_selector.rs new file mode 100644 index 0000000000..124d7f13ee --- /dev/null +++ b/crates/ui/src/components/language_selector.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; +use crate::{OrderMethod, Palette, PaletteItem}; + +#[derive(Element)] +pub struct LanguageSelector { + scroll_state: ScrollState, +} + +impl LanguageSelector { + pub fn new() -> Self { + Self { + scroll_state: ScrollState::default(), + } + } + + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + div().child( + Palette::new(self.scroll_state.clone()) + .items(vec![ + PaletteItem::new("C"), + PaletteItem::new("C++"), + PaletteItem::new("CSS"), + PaletteItem::new("Elixir"), + PaletteItem::new("Elm"), + PaletteItem::new("ERB"), + PaletteItem::new("Rust (current)"), + PaletteItem::new("Scheme"), + PaletteItem::new("TOML"), + PaletteItem::new("TypeScript"), + ]) + .placeholder("Select a language...") + .empty_string("No matches") + .default_order(OrderMethod::Ascending), + ) + } +} diff --git a/crates/ui/src/components/multi_buffer.rs b/crates/ui/src/components/multi_buffer.rs new file mode 100644 index 0000000000..d38603457a --- /dev/null +++ b/crates/ui/src/components/multi_buffer.rs @@ -0,0 +1,42 @@ +use std::marker::PhantomData; + +use crate::prelude::*; +use crate::{v_stack, Buffer, Icon, IconButton, Label, LabelSize}; + +#[derive(Element)] +pub struct MultiBuffer { + view_type: PhantomData, + buffers: Vec, +} + +impl MultiBuffer { + pub fn new(buffers: Vec) -> Self { + Self { + view_type: PhantomData, + buffers, + } + } + + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + v_stack() + .w_full() + .h_full() + .flex_1() + .children(self.buffers.clone().into_iter().map(|buffer| { + v_stack() + .child( + div() + .flex() + .items_center() + .justify_between() + .p_4() + .fill(theme.lowest.base.default.background) + .child(Label::new("main.rs").size(LabelSize::Small)) + .child(IconButton::new(Icon::ArrowUpRight)), + ) + .child(buffer) + })) + } +} diff --git a/crates/ui/src/components/palette.rs b/crates/ui/src/components/palette.rs index 430ab8be63..16001e50c1 100644 --- a/crates/ui/src/components/palette.rs +++ b/crates/ui/src/components/palette.rs @@ -93,19 +93,17 @@ impl Palette { .fill(theme.lowest.base.hovered.background) .active() .fill(theme.lowest.base.pressed.background) - .child( - PaletteItem::new(item.label) - .keybinding(item.keybinding.clone()), - ) + .child(item.clone()) })), ), ) } } -#[derive(Element)] +#[derive(Element, Clone)] pub struct PaletteItem { pub label: &'static str, + pub sublabel: Option<&'static str>, pub keybinding: Option, } @@ -113,6 +111,7 @@ impl PaletteItem { pub fn new(label: &'static str) -> Self { Self { label, + sublabel: None, keybinding: None, } } @@ -122,6 +121,11 @@ impl PaletteItem { self } + pub fn sublabel>>(mut self, sublabel: L) -> Self { + self.sublabel = sublabel.into(); + self + } + pub fn keybinding(mut self, keybinding: K) -> Self where K: Into>, @@ -138,7 +142,11 @@ impl PaletteItem { .flex_row() .grow() .justify_between() - .child(Label::new(self.label)) + .child( + v_stack() + .child(Label::new(self.label)) + .children(self.sublabel.map(|sublabel| Label::new(sublabel))), + ) .children(self.keybinding.clone()) } } diff --git a/crates/ui/src/components/recent_projects.rs b/crates/ui/src/components/recent_projects.rs new file mode 100644 index 0000000000..6aca6631b9 --- /dev/null +++ b/crates/ui/src/components/recent_projects.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; +use crate::{OrderMethod, Palette, PaletteItem}; + +#[derive(Element)] +pub struct RecentProjects { + scroll_state: ScrollState, +} + +impl RecentProjects { + pub fn new() -> Self { + Self { + scroll_state: ScrollState::default(), + } + } + + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + div().child( + Palette::new(self.scroll_state.clone()) + .items(vec![ + PaletteItem::new("zed").sublabel("~/projects/zed"), + PaletteItem::new("saga").sublabel("~/projects/saga"), + PaletteItem::new("journal").sublabel("~/journal"), + PaletteItem::new("dotfiles").sublabel("~/dotfiles"), + PaletteItem::new("zed.dev").sublabel("~/projects/zed.dev"), + PaletteItem::new("laminar").sublabel("~/projects/laminar"), + ]) + .placeholder("Recent Projects...") + .empty_string("No matches") + .default_order(OrderMethod::Ascending), + ) + } +} diff --git a/crates/ui/src/components/theme_selector.rs b/crates/ui/src/components/theme_selector.rs new file mode 100644 index 0000000000..e6f5237afe --- /dev/null +++ b/crates/ui/src/components/theme_selector.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; +use crate::{OrderMethod, Palette, PaletteItem}; + +#[derive(Element)] +pub struct ThemeSelector { + scroll_state: ScrollState, +} + +impl ThemeSelector { + pub fn new() -> Self { + Self { + scroll_state: ScrollState::default(), + } + } + + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + div().child( + Palette::new(self.scroll_state.clone()) + .items(vec![ + PaletteItem::new("One Dark"), + PaletteItem::new("Rosé Pine"), + PaletteItem::new("Rosé Pine Moon"), + PaletteItem::new("Sandcastle"), + PaletteItem::new("Solarized Dark"), + PaletteItem::new("Summercamp"), + PaletteItem::new("Atelier Cave Light"), + PaletteItem::new("Atelier Dune Light"), + PaletteItem::new("Atelier Estuary Light"), + PaletteItem::new("Atelier Forest Light"), + PaletteItem::new("Atelier Heath Light"), + ]) + .placeholder("Select Theme...") + .empty_string("No matches") + .default_order(OrderMethod::Ascending), + ) + } +} diff --git a/crates/ui/src/components/toast.rs b/crates/ui/src/components/toast.rs new file mode 100644 index 0000000000..c299cdd6bc --- /dev/null +++ b/crates/ui/src/components/toast.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +pub enum ToastOrigin { + #[default] + Bottom, + BottomRight, +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +pub enum ToastVariant { + #[default] + Toast, + Status, +} + +/// A toast is a small, temporary window that appears to show a message to the user +/// or indicate a required action. +/// +/// Toasts should not persist on the screen for more than a few seconds unless +/// they are actively showing the a process in progress. +/// +/// Only one toast may be visible at a time. +#[derive(Element)] +pub struct Toast { + origin: ToastOrigin, + children: HackyChildren, + payload: HackyChildrenPayload, +} + +impl Toast { + pub fn new( + origin: ToastOrigin, + children: HackyChildren, + payload: HackyChildrenPayload, + ) -> Self { + Self { + origin, + children, + payload, + } + } + + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let color = ThemeColor::new(cx); + + let mut div = div(); + + if self.origin == ToastOrigin::Bottom { + div = div.right_1_2(); + } else { + div = div.right_4(); + } + + div.absolute() + .bottom_4() + .flex() + .py_2() + .px_1p5() + .min_w_40() + .rounded_md() + .fill(color.elevated_surface) + .max_w_64() + .children_any((self.children)(cx, self.payload.as_ref())) + } +} diff --git a/crates/ui/src/components/workspace.rs b/crates/ui/src/components/workspace.rs index b609546f7f..b3d375bd64 100644 --- a/crates/ui/src/components/workspace.rs +++ b/crates/ui/src/components/workspace.rs @@ -82,6 +82,7 @@ impl WorkspaceElement { ); div() + .relative() .size_full() .flex() .flex_col() @@ -169,5 +170,17 @@ impl WorkspaceElement { ), ) .child(StatusBar::new()) + // An example of a toast is below + // Currently because of stacking order this gets obscured by other elements + + // .child(Toast::new( + // ToastOrigin::Bottom, + // |_, payload| { + // let theme = payload.downcast_ref::>().unwrap(); + + // vec![Label::new("label").into_any()] + // }, + // Box::new(theme.clone()), + // )) } } diff --git a/crates/ui/src/elements/icon.rs b/crates/ui/src/elements/icon.rs index 6d4053a4ae..26bf7dab22 100644 --- a/crates/ui/src/elements/icon.rs +++ b/crates/ui/src/elements/icon.rs @@ -60,6 +60,7 @@ pub enum Icon { ChevronUp, Close, ExclamationTriangle, + ExternalLink, File, FileGeneric, FileDoc, @@ -109,6 +110,7 @@ impl Icon { Icon::ChevronUp => "icons/chevron_up.svg", Icon::Close => "icons/x.svg", Icon::ExclamationTriangle => "icons/warning.svg", + Icon::ExternalLink => "icons/external_link.svg", Icon::File => "icons/file.svg", Icon::FileGeneric => "icons/file_icons/file.svg", Icon::FileDoc => "icons/file_icons/book.svg", diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index b19b2becd1..e0da3579e2 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -29,6 +29,26 @@ impl SystemColor { } } +#[derive(Clone, Copy)] +pub struct ThemeColor { + pub border: Hsla, + pub border_variant: Hsla, + /// The background color of an elevated surface, like a modal, tooltip or toast. + pub elevated_surface: Hsla, +} + +impl ThemeColor { + pub fn new(cx: &WindowContext) -> Self { + let theme = theme(cx); + + Self { + border: theme.lowest.base.default.border, + border_variant: theme.lowest.variant.default.border, + elevated_surface: theme.middle.base.default.background, + } + } +} + #[derive(Default, PartialEq, EnumIter, Clone, Copy)] pub enum HighlightColor { #[default] From e802c072f7e3e32503355e44cbaa32b5e382e14d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:16:12 -0400 Subject: [PATCH 009/141] Start hacking in autocomplete docs --- crates/editor/src/editor.rs | 91 ++++--- crates/editor/src/hover_popover.rs | 392 ++++++++++++++++++++++++----- crates/theme/src/theme.rs | 8 +- styles/src/style_tree/editor.ts | 8 +- 4 files changed, 382 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 24ffa64a6a..b1c5e35703 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -859,7 +859,6 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - project: Option>, completions: Arc<[Completion]>, match_candidates: Vec, matches: Arc<[StringMatch]>, @@ -903,42 +902,17 @@ impl CompletionsMenu { fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} - let language_servers = self.project.as_ref().map(|project| { - project - .read(cx) - .language_servers_for_buffer(self.buffer.read(cx), cx) - .filter(|(_, server)| server.capabilities().completion_provider.is_some()) - .map(|(adapter, server)| (server.server_id(), adapter.short_name)) - .collect::>() - }); - let needs_server_name = language_servers - .as_ref() - .map_or(false, |servers| servers.len() > 1); - - let get_server_name = - move |lookup_server_id: lsp::LanguageServerId| -> Option<&'static str> { - language_servers - .iter() - .flatten() - .find_map(|(server_id, server_name)| { - if *server_id == lookup_server_id { - Some(*server_name) - } else { - None - } - }) - }; - let widest_completion_ix = self .matches .iter() .enumerate() .max_by_key(|(_, mat)| { let completion = &self.completions[mat.candidate_id]; - let mut len = completion.label.text.chars().count(); + let documentation = &completion.lsp_completion.documentation; - if let Some(server_name) = get_server_name(completion.server_id) { - len += server_name.chars().count(); + let mut len = completion.label.text.chars().count(); + if let Some(lsp::Documentation::String(text)) = documentation { + len += text.chars().count(); } len @@ -948,8 +922,16 @@ impl CompletionsMenu { let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let container_style = style.autocomplete.container; - UniformList::new( + + let alongside_docs_text_style = TextStyle { + soft_wrap: true, + ..style.text.clone() + }; + let alongside_docs_width = style.autocomplete.alongside_docs_width; + let alongside_docs_container_style = style.autocomplete.alongside_docs_container; + let outer_container_style = style.autocomplete.container; + + let list = UniformList::new( self.list.clone(), matches.len(), cx, @@ -957,7 +939,9 @@ impl CompletionsMenu { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; + let documentation = &completion.lsp_completion.documentation; let item_ix = start_ix + ix; + items.push( MouseEventHandler::new::( mat.candidate_id, @@ -986,22 +970,18 @@ impl CompletionsMenu { ), ); - if let Some(server_name) = get_server_name(completion.server_id) { + if let Some(lsp::Documentation::String(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { - if !needs_server_name { - return None; - } - let text_style = TextStyle { - color: style.autocomplete.server_name_color, + color: style.autocomplete.inline_docs_color, font_size: style.text.font_size - * style.autocomplete.server_name_size_percent, + * style.autocomplete.inline_docs_size_percent, ..style.text.clone() }; - let label = Text::new(server_name, text_style) + let label = Text::new(text.clone(), text_style) .aligned() .constrained() .dynamically(move |constraint, _, _| { @@ -1021,7 +1001,7 @@ impl CompletionsMenu { .with_style( style .autocomplete - .server_name_container, + .inline_docs_container, ) .into_any(), ) @@ -1065,10 +1045,29 @@ impl CompletionsMenu { } }, ) - .with_width_from_item(widest_completion_ix) - .contained() - .with_style(container_style) - .into_any() + .with_width_from_item(widest_completion_ix); + + Flex::row() + .with_child(list) + .with_children({ + let completion = &self.completions[selected_item]; + let documentation = &completion.lsp_completion.documentation; + + if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + Some( + Text::new(content.value.clone(), alongside_docs_text_style) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), + ) + } else { + None + } + }) + .contained() + .with_style(outer_container_style) + .into_any() } pub async fn filter(&mut self, query: Option<&str>, executor: Arc) { @@ -3150,7 +3149,6 @@ impl Editor { }); let id = post_inc(&mut self.next_completion_id); - let project = self.project.clone(); let task = cx.spawn(|this, mut cx| { async move { let menu = if let Some(completions) = completions.await.log_err() { @@ -3169,7 +3167,6 @@ impl Editor { }) .collect(), buffer, - project, completions: completions.into(), matches: Vec::new().into(), selected_item: 0, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 553cb321c3..69b5562c34 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{InlayHighlight, RangeInEditor}, + link_go_to_definition::{DocumentRange, InlayRange}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,12 +8,12 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + fonts::{HighlightStyle, Underline, Weight}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; -use rich_text::{new_paragraph, render_code, render_markdown_mut, RichText}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -50,18 +50,19 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub range: InlayHighlight, + pub triggered_from: InlayOffset, + pub range: InlayRange, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_start: InlayOffset, + hint_range: Range, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_start { - let mut hovered_character = (hovered_offset - hint_start).0; - let mut part_start = hint_start; + if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { + let mut hovered_character = (hovered_offset - hint_range.start).0; + let mut part_start = hint_range.start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -87,8 +88,10 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let RangeInEditor::Inlay(range) = symbol_range { - if range == &inlay_hover.range { + if let DocumentRange::Inlay(range) = symbol_range { + if (range.highlight_start..range.highlight_end) + .contains(&inlay_hover.triggered_from) + { // Hover triggered from same location as last time. Don't show again. return; } @@ -96,6 +99,18 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } + let snapshot = editor.snapshot(cx); + // Don't request again if the location is the same as the previous request + if let Some(triggered_from) = editor.hover_state.triggered_from { + if inlay_hover.triggered_from + == snapshot + .display_snapshot + .anchor_to_inlay_offset(triggered_from) + { + return; + } + } + let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -107,7 +122,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), + symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, rendered_content: None, @@ -311,7 +326,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: RangeInEditor::Text(range), + symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, rendered_content: None, @@ -346,43 +361,237 @@ fn show_hover( } fn render_blocks( + theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option<&Arc>, -) -> RichText { - let mut data = RichText { - text: Default::default(), - highlights: Default::default(), - region_ranges: Default::default(), - regions: Default::default(), - }; + style: &EditorStyle, +) -> RenderedInfo { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut data.text, &mut Vec::new()); - data.text.push_str(&block.text); + new_paragraph(&mut text, &mut Vec::new()); + text.push_str(&block.text); } + HoverBlockKind::Markdown => { - render_markdown_mut(&block.text, language_registry, language, &mut data) + use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&block.text, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code( + &mut text, + &mut highlights, + t.as_ref(), + language, + style, + ); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.cloned() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } } + HoverBlockKind::Code { language } => { if let Some(language) = language_registry .language_for_name(language) .now_or_never() .and_then(Result::ok) { - render_code(&mut data.text, &mut data.highlights, &block.text, &language); + render_code(&mut text, &mut highlights, &block.text, &language, style); } else { - data.text.push_str(&block.text); + text.push_str(&block.text); } } } } - data.text = data.text.trim().to_string(); + RenderedInfo { + theme_id, + text: text.trim().to_string(), + highlights, + region_ranges, + regions, + } +} - data +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } } #[derive(Default)] @@ -415,8 +624,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - RangeInEditor::Text(range) => &range.start, - RangeInEditor::Inlay(range) => &range.inlay_position, + DocumentRange::Text(range) => &range.start, + DocumentRange::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -442,10 +651,25 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: RangeInEditor, + symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, +} + +#[derive(Debug, Clone)] +struct RenderedInfo { + theme_id: usize, + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, } impl InfoPopover { @@ -454,24 +678,63 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { + if let Some(rendered) = &self.rendered_content { + if rendered.theme_id != style.theme_id { + self.rendered_content = None; + } + } + let rendered_content = self.rendered_content.get_or_insert_with(|| { render_blocks( + style.theme_id, &self.blocks, self.project.read(cx).languages(), self.language.as_ref(), + style, ) }); - MouseEventHandler::new::(0, cx, move |_, cx| { + MouseEventHandler::new::(0, cx, |_, cx| { + let mut region_id = 0; + let view_id = cx.view_id(); + let code_span_background_color = style.document_highlight_read_background; + let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child(rendered_content.element( - style.syntax.clone(), - style.text.clone(), - code_span_background_color, - cx, - )) + .with_child( + Text::new(rendered_content.text.clone(), style.text.clone()) + .with_highlights(rendered_content.highlights.clone()) + .with_custom_runs( + rendered_content.region_ranges.clone(), + move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::( + MouseButton::Left, + move |_, _, cx| cx.platform().open_url(&url), + ), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }, + ) + .with_soft_wrap(true), + ) .contained() .with_style(style.hover_popover.container) }) @@ -564,15 +827,13 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, - InlayId, }; use collections::BTreeSet; - use gpui::fonts::{HighlightStyle, Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; - use rich_text::Highlight; use smol::stream::StreamExt; use unindent::Unindent; use util::test::marked_text_ranges; @@ -783,7 +1044,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, _| { + cx.editor(|editor, cx| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -793,7 +1054,8 @@ mod tests { }], ); - let rendered = render_blocks(&blocks, &Default::default(), None); + let style = editor.style(cx); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -985,7 +1247,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(&blocks, &Default::default(), None); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -996,21 +1258,8 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); - - let rendered_highlights: Vec<_> = rendered - .highlights - .iter() - .filter_map(|(range, highlight)| { - let style = match highlight { - Highlight::Id(id) => id.style(&style.syntax)?, - Highlight::Highlight(style) => style.clone(), - }; - Some((range.clone(), style)) - }) - .collect(); - assert_eq!( - rendered_highlights, expected_highlights, + rendered.highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1244,16 +1493,25 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + + let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len()..": ".len() + new_type_label.len(), + highlight_start: expected_new_type_label_start, + highlight_end: InlayOffset( + expected_new_type_label_start.0 + new_type_label.len() + ), }), "Popover range should match the new type label part" ); @@ -1301,17 +1559,23 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); + let entire_inlay_start = snapshot.display_point_to_inlay_offset( + inlay_range.start.to_display_point(&snapshot), + Bias::Left, + ); + let expected_struct_label_start = + InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - RangeInEditor::Inlay(InlayHighlight { - inlay: InlayId::Hint(0), + DocumentRange::Inlay(InlayRange { inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - range: ": ".len() + new_type_label.len() + "<".len() - ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), + highlight_start: expected_struct_label_start, + highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), }), "Popover range should match the struct label part" ); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e534ba4260..9f7530ec18 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,9 +867,11 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, - pub server_name_container: ContainerStyle, - pub server_name_color: Color, - pub server_name_size_percent: f32, + pub inline_docs_container: ContainerStyle, + pub inline_docs_color: Color, + pub inline_docs_size_percent: f32, + pub alongside_docs_width: f32, + pub alongside_docs_container: ContainerStyle, } #[derive(Clone, Copy, Default, Deserialize, JsonSchema)] diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e55a73c365..37d6c4ea1e 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,9 +206,11 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, - server_name_container: { padding: { left: 40 } }, - server_name_color: text(theme.middle, "sans", "disabled", {}).color, - server_name_size_percent: 0.75, + inline_docs_container: { padding: { left: 40 } }, + inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, + inline_docs_size_percent: 0.75, + alongside_docs_width: 400, + alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { background: background(theme.middle), From 1584dae9c211c65120825314e4a6b5fbda39cd1d Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 5 Sep 2023 22:23:16 -0400 Subject: [PATCH 010/141] Actually display the correct completion's doc --- crates/editor/src/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1c5e35703..ba2b50c1c1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1050,7 +1050,8 @@ impl CompletionsMenu { Flex::row() .with_child(list) .with_children({ - let completion = &self.completions[selected_item]; + let mat = &self.matches[selected_item]; + let completion = &self.completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { From 370a3cafd0381b088ca1ac2122ecea968c7f9839 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 14 Sep 2023 15:24:46 -0400 Subject: [PATCH 011/141] Add markdown rendering to alongside completion docs --- crates/editor/src/editor.rs | 51 ++++--- crates/editor/src/markdown.rs | 246 ++++++++++++++++++++++++++++++++ styles/src/style_tree/editor.ts | 2 +- 3 files changed, 280 insertions(+), 19 deletions(-) create mode 100644 crates/editor/src/markdown.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ba2b50c1c1..2035bd35f0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,6 +9,7 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; +mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -845,11 +846,12 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, + editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -899,7 +901,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + editor: &Editor, + style: EditorStyle, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -923,18 +930,12 @@ impl CompletionsMenu { let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_text_style = TextStyle { - soft_wrap: true, - ..style.text.clone() - }; let alongside_docs_width = style.autocomplete.alongside_docs_width; let alongside_docs_container_style = style.autocomplete.alongside_docs_container; let outer_container_style = style.autocomplete.container; - let list = UniformList::new( - self.list.clone(), - matches.len(), - cx, + let list = UniformList::new(self.list.clone(), matches.len(), cx, { + let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; for (ix, mat) in matches[range].iter().enumerate() { @@ -1043,8 +1044,8 @@ impl CompletionsMenu { .into_any(), ); } - }, - ) + } + }) .with_width_from_item(widest_completion_ix); Flex::row() @@ -1055,12 +1056,26 @@ impl CompletionsMenu { let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { + let registry = editor + .project + .as_ref() + .unwrap() + .read(cx) + .languages() + .clone(); + let language = self.buffer.read(cx).language().map(Arc::clone); Some( - Text::new(content.value.clone(), alongside_docs_text_style) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + ) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None @@ -3985,7 +4000,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + .map(|menu| menu.render(cursor_position, self, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs new file mode 100644 index 0000000000..3ea8db34b3 --- /dev/null +++ b/crates/editor/src/markdown.rs @@ -0,0 +1,246 @@ +use std::ops::Range; +use std::sync::Arc; + +use futures::FutureExt; +use gpui::{ + elements::Text, + fonts::{HighlightStyle, Underline, Weight}, + platform::{CursorStyle, MouseButton}, + CursorRegion, MouseRegion, ViewContext, +}; +use language::{Language, LanguageRegistry}; +use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; + +use crate::{Editor, EditorStyle}; + +#[derive(Debug, Clone)] +struct RenderedRegion { + code: bool, + link_url: Option, +} + +pub fn render_markdown( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + let mut text = String::new(); + let mut highlights = Vec::new(); + let mut region_ranges = Vec::new(); + let mut regions = Vec::new(); + + let mut bold_depth = 0; + let mut italic_depth = 0; + let mut link_url = None; + let mut current_language = None; + let mut list_stack = Vec::new(); + + for event in Parser::new_ext(&markdown, Options::all()) { + let prev_len = text.len(); + match event { + Event::Text(t) => { + if let Some(language) = ¤t_language { + render_code(&mut text, &mut highlights, t.as_ref(), language, style); + } else { + text.push_str(t.as_ref()); + + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.weight = Some(Weight::BOLD); + } + if italic_depth > 0 { + style.italic = Some(true); + } + if let Some(link_url) = link_url.clone() { + region_ranges.push(prev_len..text.len()); + regions.push(RenderedRegion { + link_url: Some(link_url), + code: false, + }); + style.underline = Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style != HighlightStyle::default() { + let mut new_highlight = true; + if let Some((last_range, last_style)) = highlights.last_mut() { + if last_range.end == prev_len && last_style == &style { + last_range.end = text.len(); + new_highlight = false; + } + } + if new_highlight { + highlights.push((prev_len..text.len(), style)); + } + } + } + } + + Event::Code(t) => { + text.push_str(t.as_ref()); + region_ranges.push(prev_len..text.len()); + if link_url.is_some() { + highlights.push(( + prev_len..text.len(), + HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), + ..Default::default() + }, + )); + } + regions.push(RenderedRegion { + code: true, + link_url: link_url.clone(), + }); + } + + Event::Start(tag) => match tag { + Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + + Tag::Heading(_, _, _) => { + new_paragraph(&mut text, &mut list_stack); + bold_depth += 1; + } + + Tag::CodeBlock(kind) => { + new_paragraph(&mut text, &mut list_stack); + current_language = if let CodeBlockKind::Fenced(language) = kind { + language_registry + .language_for_name(language.as_ref()) + .now_or_never() + .and_then(Result::ok) + } else { + language.clone() + } + } + + Tag::Emphasis => italic_depth += 1, + + Tag::Strong => bold_depth += 1, + + Tag::Link(_, url, _) => link_url = Some(url.to_string()), + + Tag::List(number) => { + list_stack.push((number, false)); + } + + Tag::Item => { + let len = list_stack.len(); + if let Some((list_number, has_content)) = list_stack.last_mut() { + *has_content = false; + if !text.is_empty() && !text.ends_with('\n') { + text.push('\n'); + } + for _ in 0..len - 1 { + text.push_str(" "); + } + if let Some(number) = list_number { + text.push_str(&format!("{}. ", number)); + *number += 1; + *has_content = false; + } else { + text.push_str("- "); + } + } + } + + _ => {} + }, + + Event::End(tag) => match tag { + Tag::Heading(_, _, _) => bold_depth -= 1, + Tag::CodeBlock(_) => current_language = None, + Tag::Emphasis => italic_depth -= 1, + Tag::Strong => bold_depth -= 1, + Tag::Link(_, _, _) => link_url = None, + Tag::List(_) => drop(list_stack.pop()), + _ => {} + }, + + Event::HardBreak => text.push('\n'), + + Event::SoftBreak => text.push(' '), + + _ => {} + } + } + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +fn render_code( + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + content: &str, + language: &Arc, + style: &EditorStyle, +) { + let prev_len = text.len(); + text.push_str(content); + for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { + if let Some(style) = highlight_id.style(&style.syntax) { + highlights.push((prev_len + range.start..prev_len + range.end, style)); + } + } +} + +fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { + let mut is_subsequent_paragraph_of_list = false; + if let Some((_, has_content)) = list_stack.last_mut() { + if *has_content { + is_subsequent_paragraph_of_list = true; + } else { + *has_content = true; + return; + } + } + + if !text.is_empty() { + if !text.ends_with('\n') { + text.push('\n'); + } + text.push('\n'); + } + for _ in 0..list_stack.len().saturating_sub(1) { + text.push_str(" "); + } + if is_subsequent_paragraph_of_list { + text.push_str(" "); + } +} diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 37d6c4ea1e..e7717583a8 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -209,7 +209,7 @@ export default function editor(): any { inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 400, + alongside_docs_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From e8be14e5d64d6b574ea626ff50e59e84875ebf39 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 11:51:57 -0400 Subject: [PATCH 012/141] Merge info popover's and autocomplete docs' markdown rendering --- crates/editor/src/hover_popover.rs | 230 ++++------------------------- crates/editor/src/markdown.rs | 102 ++++++++----- 2 files changed, 87 insertions(+), 245 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 69b5562c34..16ecb2dc01 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,7 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, + markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -8,7 +9,7 @@ use futures::FutureExt; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::{HighlightStyle, Underline, Weight}, + fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; @@ -364,7 +365,7 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: Option<&Arc>, + language: &Option>, style: &EditorStyle, ) -> RenderedInfo { let mut text = String::new(); @@ -375,160 +376,20 @@ fn render_blocks( for block in blocks { match &block.kind { HoverBlockKind::PlainText => { - new_paragraph(&mut text, &mut Vec::new()); + markdown::new_paragraph(&mut text, &mut Vec::new()); text.push_str(&block.text); } - HoverBlockKind::Markdown => { - use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; - - let mut bold_depth = 0; - let mut italic_depth = 0; - let mut link_url = None; - let mut current_language = None; - let mut list_stack = Vec::new(); - - for event in Parser::new_ext(&block.text, Options::all()) { - let prev_len = text.len(); - match event { - Event::Text(t) => { - if let Some(language) = ¤t_language { - render_code( - &mut text, - &mut highlights, - t.as_ref(), - language, - style, - ); - } else { - text.push_str(t.as_ref()); - - let mut style = HighlightStyle::default(); - if bold_depth > 0 { - style.weight = Some(Weight::BOLD); - } - if italic_depth > 0 { - style.italic = Some(true); - } - if let Some(link_url) = link_url.clone() { - region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { - link_url: Some(link_url), - code: false, - }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style != HighlightStyle::default() { - let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == prev_len && last_style == &style { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - highlights.push((prev_len..text.len(), style)); - } - } - } - } - - Event::Code(t) => { - text.push_str(t.as_ref()); - region_ranges.push(prev_len..text.len()); - if link_url.is_some() { - highlights.push(( - prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }, - )); - } - regions.push(RenderedRegion { - code: true, - link_url: link_url.clone(), - }); - } - - Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), - - Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); - bold_depth += 1; - } - - Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); - current_language = if let CodeBlockKind::Fenced(language) = kind { - language_registry - .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) - } else { - language.cloned() - } - } - - Tag::Emphasis => italic_depth += 1, - - Tag::Strong => bold_depth += 1, - - Tag::Link(_, url, _) => link_url = Some(url.to_string()), - - Tag::List(number) => { - list_stack.push((number, false)); - } - - Tag::Item => { - let len = list_stack.len(); - if let Some((list_number, has_content)) = list_stack.last_mut() { - *has_content = false; - if !text.is_empty() && !text.ends_with('\n') { - text.push('\n'); - } - for _ in 0..len - 1 { - text.push_str(" "); - } - if let Some(number) = list_number { - text.push_str(&format!("{}. ", number)); - *number += 1; - *has_content = false; - } else { - text.push_str("- "); - } - } - } - - _ => {} - }, - - Event::End(tag) => match tag { - Tag::Heading(_, _, _) => bold_depth -= 1, - Tag::CodeBlock(_) => current_language = None, - Tag::Emphasis => italic_depth -= 1, - Tag::Strong => bold_depth -= 1, - Tag::Link(_, _, _) => link_url = None, - Tag::List(_) => drop(list_stack.pop()), - _ => {} - }, - - Event::HardBreak => text.push('\n'), - - Event::SoftBreak => text.push(' '), - - _ => {} - } - } - } + HoverBlockKind::Markdown => markdown::render_markdown_block( + &block.text, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ), HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -536,7 +397,13 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - render_code(&mut text, &mut highlights, &block.text, &language, style); + markdown::render_code( + &mut text, + &mut highlights, + &block.text, + &language, + style, + ); } else { text.push_str(&block.text); } @@ -553,47 +420,6 @@ fn render_blocks( } } -fn render_code( - text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, - content: &str, - language: &Arc, - style: &EditorStyle, -) { - let prev_len = text.len(); - text.push_str(content); - for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } - } -} - -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { - let mut is_subsequent_paragraph_of_list = false; - if let Some((_, has_content)) = list_stack.last_mut() { - if *has_content { - is_subsequent_paragraph_of_list = true; - } else { - *has_content = true; - return; - } - } - - if !text.is_empty() { - if !text.ends_with('\n') { - text.push('\n'); - } - text.push('\n'); - } - for _ in 0..list_stack.len().saturating_sub(1) { - text.push_str(" "); - } - if is_subsequent_paragraph_of_list { - text.push_str(" "); - } -} - #[derive(Default)] pub struct HoverState { pub info_popover: Option, @@ -666,12 +492,6 @@ struct RenderedInfo { regions: Vec, } -#[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, -} - impl InfoPopover { pub fn render( &mut self, @@ -689,7 +509,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - self.language.as_ref(), + &self.language, style, ) }); @@ -829,7 +649,7 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{Underline, Weight}; use indoc::indoc; use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; @@ -1055,7 +875,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1247,7 +1067,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/editor/src/markdown.rs b/crates/editor/src/markdown.rs index 3ea8db34b3..df5041c0db 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/editor/src/markdown.rs @@ -14,9 +14,9 @@ use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; use crate::{Editor, EditorStyle}; #[derive(Debug, Clone)] -struct RenderedRegion { - code: bool, - link_url: Option, +pub struct RenderedRegion { + pub code: bool, + pub link_url: Option, } pub fn render_markdown( @@ -31,6 +31,59 @@ pub fn render_markdown( let mut region_ranges = Vec::new(); let mut regions = Vec::new(); + render_markdown_block( + markdown, + language_registry, + language, + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ); + + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(text, style.text.clone()) + .with_highlights(highlights) + .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + +pub fn render_markdown_block( + markdown: &str, + language_registry: &Arc, + language: &Option>, + style: &EditorStyle, + text: &mut String, + highlights: &mut Vec<(Range, HighlightStyle)>, + region_ranges: &mut Vec>, + regions: &mut Vec, +) { let mut bold_depth = 0; let mut italic_depth = 0; let mut link_url = None; @@ -42,7 +95,7 @@ pub fn render_markdown( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(&mut text, &mut highlights, t.as_ref(), language, style); + render_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -102,15 +155,15 @@ pub fn render_markdown( } Event::Start(tag) => match tag { - Tag::Paragraph => new_paragraph(&mut text, &mut list_stack), + Tag::Paragraph => new_paragraph(text, &mut list_stack), Tag::Heading(_, _, _) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); bold_depth += 1; } Tag::CodeBlock(kind) => { - new_paragraph(&mut text, &mut list_stack); + new_paragraph(text, &mut list_stack); current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) @@ -171,40 +224,9 @@ pub fn render_markdown( _ => {} } } - - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) } -fn render_code( +pub fn render_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, @@ -220,7 +242,7 @@ fn render_code( } } -fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { +pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { if *has_content { From ca88717f0c3145b441f3efeb81d53d578b1a9c7e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 15 Sep 2023 15:12:04 -0400 Subject: [PATCH 013/141] Make completion docs scrollable --- crates/editor/src/editor.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2035bd35f0..2f02ac59b0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1064,18 +1064,22 @@ impl CompletionsMenu { .languages() .clone(); let language = self.buffer.read(cx).language().map(Arc::clone); + + enum CompletionDocsMarkdown {} Some( - crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - ) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), + Flex::column() + .scrollable::(0, None, cx) + .with_child(crate::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style, + cx, + )) + .constrained() + .with_width(alongside_docs_width) + .contained() + .with_style(alongside_docs_container_style), ) } else { None From 77ba25328cbeee3bfec5c3acef71fa8ff4394870 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 15:10:48 -0400 Subject: [PATCH 014/141] Most of getting completion documentation resolved & cached MD parsing --- Cargo.lock | 2 +- crates/editor/Cargo.toml | 1 - crates/editor/src/editor.rs | 170 ++++++++++++++++---- crates/editor/src/hover_popover.rs | 6 +- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 2 + crates/language/src/language.rs | 1 + crates/{editor => language}/src/markdown.rs | 90 ++++++----- crates/language/src/proto.rs | 1 + crates/lsp/src/lsp.rs | 14 +- crates/project/src/lsp_command.rs | 1 + crates/zed/src/languages.rs | 4 +- 12 files changed, 217 insertions(+), 76 deletions(-) rename crates/{editor => language}/src/markdown.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index c971846a5d..147760ab14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2404,7 +2404,6 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", - "pulldown-cmark", "rand 0.8.5", "rich_text", "rpc", @@ -3990,6 +3989,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "pulldown-cmark", "rand 0.8.5", "regex", "rpc", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 2c3d6227a9..d03e1c1106 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -57,7 +57,6 @@ log.workspace = true ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true -pulldown-cmark = { version = "0.9.2", default-features = false } rand.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2f02ac59b0..c0d2b4ee0b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9,7 +9,6 @@ mod highlight_matching_bracket; mod hover_popover; pub mod items; mod link_go_to_definition; -mod markdown; mod mouse_context_menu; pub mod movement; pub mod multi_buffer; @@ -78,6 +77,7 @@ pub use multi_buffer::{ ToPoint, }; use ordered_float::OrderedFloat; +use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; use rpc::proto::PeerId; @@ -788,10 +788,14 @@ enum ContextMenu { } impl ContextMenu { - fn select_first(&mut self, cx: &mut ViewContext) -> bool { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -800,10 +804,14 @@ impl ContextMenu { } } - fn select_prev(&mut self, cx: &mut ViewContext) -> bool { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -812,10 +820,14 @@ impl ContextMenu { } } - fn select_next(&mut self, cx: &mut ViewContext) -> bool { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -824,10 +836,14 @@ impl ContextMenu { } } - fn select_last(&mut self, cx: &mut ViewContext) -> bool { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -861,7 +877,7 @@ struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, - completions: Arc<[Completion]>, + completions: Arc>>, match_candidates: Vec, matches: Arc<[StringMatch]>, selected_item: usize, @@ -869,34 +885,115 @@ struct CompletionsMenu { } impl CompletionsMenu { - fn select_first(&mut self, cx: &mut ViewContext) { + fn select_first( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_prev(&mut self, cx: &mut ViewContext) { + fn select_prev( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_next(&mut self, cx: &mut ViewContext) { + fn select_next( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } - fn select_last(&mut self, cx: &mut ViewContext) { + fn select_last( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } + fn attempt_resolve_selected_completion( + &mut self, + project: Option<&ModelHandle>, + cx: &mut ViewContext, + ) { + println!("attempt_resolve_selected_completion"); + let index = self.matches[dbg!(self.selected_item)].candidate_id; + dbg!(index); + let Some(project) = project else { + println!("no project"); + return; + }; + + let completions = self.completions.clone(); + let completions_guard = completions.read(); + let completion = &completions_guard[index]; + if completion.lsp_completion.documentation.is_some() { + println!("has existing documentation"); + return; + } + + let server_id = completion.server_id; + let completion = completion.lsp_completion.clone(); + drop(completions_guard); + + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + println!("no server"); + return; + }; + + let can_resolve = server + .capabilities() + .completion_provider + .as_ref() + .and_then(|options| options.resolve_provider) + .unwrap_or(false); + if !dbg!(can_resolve) { + return; + } + + cx.spawn(|this, mut cx| async move { + println!("in spawn"); + let request = server.request::(completion); + let Some(completion_item) = request.await.log_err() else { + println!("errored"); + return; + }; + + if completion_item.documentation.is_some() { + println!("got new documentation"); + let mut completions = completions.write(); + completions[index].lsp_completion.documentation = completion_item.documentation; + println!("notifying"); + _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + println!("did not get anything"); + } + }) + .detach(); + } + fn visible(&self) -> bool { !self.matches.is_empty() } @@ -914,7 +1011,8 @@ impl CompletionsMenu { .iter() .enumerate() .max_by_key(|(_, mat)| { - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; let mut len = completion.label.text.chars().count(); @@ -938,6 +1036,7 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; + let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; @@ -1052,7 +1151,8 @@ impl CompletionsMenu { .with_child(list) .with_children({ let mat = &self.matches[selected_item]; - let completion = &self.completions[mat.candidate_id]; + let completions = self.completions.read(); + let completion = &completions[mat.candidate_id]; let documentation = &completion.lsp_completion.documentation; if let Some(lsp::Documentation::MarkupContent(content)) = documentation { @@ -1069,13 +1169,12 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - .with_child(crate::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style, - cx, - )) + // .with_child(language::markdown::render_markdown( + // &content.value, + // ®istry, + // &language, + // &style, + // )) .constrained() .with_width(alongside_docs_width) .contained() @@ -1130,17 +1229,20 @@ impl CompletionsMenu { } } + let completions = self.completions.read(); matches.sort_unstable_by_key(|mat| { - let completion = &self.completions[mat.candidate_id]; + let completion = &completions[mat.candidate_id]; ( completion.lsp_completion.sort_text.as_ref(), Reverse(OrderedFloat(mat.score)), completion.sort_key(), ) }); + drop(completions); for mat in &mut matches { - let filter_start = self.completions[mat.candidate_id].label.filter_range.start; + let completions = self.completions.read(); + let filter_start = completions[mat.candidate_id].label.filter_range.start; for position in &mut mat.positions { *position += filter_start; } @@ -3187,7 +3289,7 @@ impl Editor { }) .collect(), buffer, - completions: completions.into(), + completions: Arc::new(RwLock::new(completions.into())), matches: Vec::new().into(), selected_item: 0, list: Default::default(), @@ -3196,6 +3298,9 @@ impl Editor { if menu.matches.is_empty() { None } else { + _ = this.update(&mut cx, |editor, cx| { + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + }); Some(menu) } } else { @@ -3252,7 +3357,8 @@ impl Editor { .matches .get(action.item_ix.unwrap_or(completions_menu.selected_item))?; let buffer_handle = completions_menu.buffer; - let completion = completions_menu.completions.get(mat.candidate_id)?; + let completions = completions_menu.completions.read(); + let completion = completions.get(mat.candidate_id)?; let snippet; let text; @@ -5372,7 +5478,7 @@ impl Editor { if self .context_menu .as_mut() - .map(|menu| menu.select_last(cx)) + .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) { return; @@ -5416,25 +5522,25 @@ impl Editor { pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 16ecb2dc01..ea6eac3a66 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,7 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{DocumentRange, InlayRange}, - markdown::{self, RenderedRegion}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -13,7 +12,10 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; -use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; +use language::{ + markdown::{self, RenderedRegion}, + Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, +}; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4771fc7083..d5d5bdd1af 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,6 +46,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } regex.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cd..10585633ae 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,6 +1,7 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, + markdown::RenderedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ @@ -148,6 +149,7 @@ pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, + pub alongside_documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af..12f76f1df3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -2,6 +2,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; pub mod language_settings; +pub mod markdown; mod outline; pub mod proto; mod syntax_map; diff --git a/crates/editor/src/markdown.rs b/crates/language/src/markdown.rs similarity index 79% rename from crates/editor/src/markdown.rs rename to crates/language/src/markdown.rs index df5041c0db..e033820a21 100644 --- a/crates/editor/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,6 +1,7 @@ use std::ops::Range; use std::sync::Arc; +use crate::{Language, LanguageRegistry}; use futures::FutureExt; use gpui::{ elements::Text, @@ -8,10 +9,50 @@ use gpui::{ platform::{CursorStyle, MouseButton}, CursorRegion, MouseRegion, ViewContext, }; -use language::{Language, LanguageRegistry}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; -use crate::{Editor, EditorStyle}; +#[derive(Debug, Clone)] +pub struct RenderedMarkdown { + text: String, + highlights: Vec<(Range, HighlightStyle)>, + region_ranges: Vec>, + regions: Vec, +} + +// impl RenderedMarkdown { +// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { +// let code_span_background_color = style.document_highlight_read_background; +// let view_id = cx.view_id(); +// let mut region_id = 0; +// Text::new(text, style.text.clone()) +// .with_highlights(highlights) +// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { +// region_id += 1; +// let region = regions[ix].clone(); +// if let Some(url) = region.link_url { +// scene.push_cursor_region(CursorRegion { +// bounds, +// style: CursorStyle::PointingHand, +// }); +// scene.push_mouse_region( +// MouseRegion::new::(view_id, region_id, bounds) +// .on_click::(MouseButton::Left, move |_, _, cx| { +// cx.platform().open_url(&url) +// }), +// ); +// } +// if region.code { +// scene.push_quad(gpui::Quad { +// bounds, +// background: Some(code_span_background_color), +// border: Default::default(), +// corner_radii: (2.0).into(), +// }); +// } +// }) +// .with_soft_wrap(true) +// } +// } #[derive(Debug, Clone)] pub struct RenderedRegion { @@ -23,9 +64,8 @@ pub fn render_markdown( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, - cx: &mut ViewContext, -) -> Text { + style: &theme::Editor, +) -> RenderedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -42,43 +82,19 @@ pub fn render_markdown( &mut regions, ); - let code_span_background_color = style.document_highlight_read_background; - let view_id = cx.view_id(); - let mut region_id = 0; - Text::new(text, style.text.clone()) - .with_highlights(highlights) - .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) + RenderedMarkdown { + text, + highlights, + region_ranges, + regions, + } } pub fn render_markdown_block( markdown: &str, language_registry: &Arc, language: &Option>, - style: &EditorStyle, + style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, @@ -231,7 +247,7 @@ pub fn render_code( highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, language: &Arc, - style: &EditorStyle, + style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d47..49b332b4fb 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,6 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 33581721ae..b4099e2f6e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -466,7 +466,10 @@ impl LanguageServer { completion_item: Some(CompletionItemCapability { snippet_support: Some(true), resolve_support: Some(CompletionItemCapabilityResolveSupport { - properties: vec!["additionalTextEdits".to_string()], + properties: vec![ + "documentation".to_string(), + "additionalTextEdits".to_string(), + ], }), ..Default::default() }), @@ -748,6 +751,15 @@ impl LanguageServer { ) } + // some child of string literal (be it "" or ``) which is the child of an attribute + + // + // + // + // + // const classes = "awesome "; + // + fn request_internal( next_id: &AtomicUsize, response_handlers: &Mutex>>, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8beaea5031..000fd3928c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1462,6 +1462,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), + alongside_documentation: None, server_id, lsp_completion, } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7d..3b65255a3d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From fcaf48eb4965349bedc7daab6561060686c8c757 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 22 Sep 2023 17:03:40 -0400 Subject: [PATCH 015/141] Use completion item default `data` when provided --- crates/project/src/lsp_command.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 000fd3928c..16ebb7467b 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1358,7 +1358,7 @@ impl LspCommand for GetCompletions { } } } else { - Default::default() + Vec::new() }; let completions = buffer.read_with(&cx, |buffer, _| { @@ -1370,6 +1370,14 @@ impl LspCommand for GetCompletions { completions .into_iter() .filter_map(move |mut lsp_completion| { + if let Some(response_list) = &response_list { + if let Some(item_defaults) = &response_list.item_defaults { + if let Some(data) = &item_defaults.data { + lsp_completion.data = Some(data.clone()); + } + } + } + let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. From fe62423344fedb81c17a5f6aa81b56805fc8d2bc Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 28 Sep 2023 14:16:30 -0400 Subject: [PATCH 016/141] Asynchronously request completion documentation if not present --- crates/editor/src/editor.rs | 78 +++++++++++++++++++++++---------- crates/language/src/markdown.rs | 50 +++------------------ 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c0d2b4ee0b..d35a9dd30e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -48,9 +48,9 @@ use gpui::{ impl_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton}, - serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, - Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, + CursorRegion, Element, Entity, ModelHandle, MouseRegion, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -119,6 +119,46 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub fn render_rendered_markdown( + md: &language::RenderedMarkdown, + style: &EditorStyle, + cx: &mut ViewContext, +) -> Text { + enum RenderedRenderedMarkdown {} + + let md = md.clone(); + let code_span_background_color = style.document_highlight_read_background; + let view_id = cx.view_id(); + let mut region_id = 0; + Text::new(md.text, style.text.clone()) + .with_highlights(md.highlights) + .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + region_id += 1; + let region = md.regions[ix].clone(); + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) +} + #[derive(Clone, Deserialize, PartialEq, Default)] pub struct SelectNext { #[serde(default)] @@ -938,11 +978,8 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - println!("attempt_resolve_selected_completion"); - let index = self.matches[dbg!(self.selected_item)].candidate_id; - dbg!(index); + let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { - println!("no project"); return; }; @@ -950,7 +987,6 @@ impl CompletionsMenu { let completions_guard = completions.read(); let completion = &completions_guard[index]; if completion.lsp_completion.documentation.is_some() { - println!("has existing documentation"); return; } @@ -959,7 +995,6 @@ impl CompletionsMenu { drop(completions_guard); let Some(server) = project.read(cx).language_server_for_id(server_id) else { - println!("no server"); return; }; @@ -969,26 +1004,21 @@ impl CompletionsMenu { .as_ref() .and_then(|options| options.resolve_provider) .unwrap_or(false); - if !dbg!(can_resolve) { + if !can_resolve { return; } cx.spawn(|this, mut cx| async move { - println!("in spawn"); let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { - println!("errored"); return; }; if completion_item.documentation.is_some() { - println!("got new documentation"); let mut completions = completions.write(); completions[index].lsp_completion.documentation = completion_item.documentation; - println!("notifying"); + drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - println!("did not get anything"); } }) .detach(); @@ -1169,12 +1199,16 @@ impl CompletionsMenu { Some( Flex::column() .scrollable::(0, None, cx) - // .with_child(language::markdown::render_markdown( - // &content.value, - // ®istry, - // &language, - // &style, - // )) + .with_child(render_rendered_markdown( + &language::markdown::render_markdown( + &content.value, + ®istry, + &language, + &style.theme, + ), + &style, + cx, + )) .constrained() .with_width(alongside_docs_width) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index e033820a21..4ccd2955b6 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -3,57 +3,17 @@ use std::sync::Arc; use crate::{Language, LanguageRegistry}; use futures::FutureExt; -use gpui::{ - elements::Text, - fonts::{HighlightStyle, Underline, Weight}, - platform::{CursorStyle, MouseButton}, - CursorRegion, MouseRegion, ViewContext, -}; +use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct RenderedMarkdown { - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + pub text: String, + pub highlights: Vec<(Range, HighlightStyle)>, + pub region_ranges: Vec>, + pub regions: Vec, } -// impl RenderedMarkdown { -// pub fn render(&self, style: &theme::Editor, cx: &mut ViewContext) -> Text { -// let code_span_background_color = style.document_highlight_read_background; -// let view_id = cx.view_id(); -// let mut region_id = 0; -// Text::new(text, style.text.clone()) -// .with_highlights(highlights) -// .with_custom_runs(region_ranges, move |ix, bounds, scene, _| { -// region_id += 1; -// let region = regions[ix].clone(); -// if let Some(url) = region.link_url { -// scene.push_cursor_region(CursorRegion { -// bounds, -// style: CursorStyle::PointingHand, -// }); -// scene.push_mouse_region( -// MouseRegion::new::(view_id, region_id, bounds) -// .on_click::(MouseButton::Left, move |_, _, cx| { -// cx.platform().open_url(&url) -// }), -// ); -// } -// if region.code { -// scene.push_quad(gpui::Quad { -// bounds, -// background: Some(code_span_background_color), -// border: Default::default(), -// corner_radii: (2.0).into(), -// }); -// } -// }) -// .with_soft_wrap(true) -// } -// } - #[derive(Debug, Clone)] pub struct RenderedRegion { pub code: bool, From b8876f2b17307917bf24b20ffcdd090f9dc5126e Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 10:58:08 -0400 Subject: [PATCH 017/141] Preparse documentation markdown when resolving completion --- crates/editor/src/editor.rs | 131 ++++++++++++++++------------- crates/editor/src/hover_popover.rs | 26 +++--- crates/language/src/buffer.rs | 44 +++++++++- crates/language/src/markdown.rs | 30 +++---- crates/language/src/proto.rs | 2 +- crates/project/src/lsp_command.rs | 2 +- 6 files changed, 144 insertions(+), 91 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d35a9dd30e..0f117cde1b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,12 +119,12 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_rendered_markdown( - md: &language::RenderedMarkdown, +pub fn render_parsed_markdown( + md: &language::ParsedMarkdown, style: &EditorStyle, cx: &mut ViewContext, ) -> Text { - enum RenderedRenderedMarkdown {} + enum RenderedMarkdown {} let md = md.clone(); let code_span_background_color = style.document_highlight_read_background; @@ -141,7 +141,7 @@ pub fn render_rendered_markdown( style: CursorStyle::PointingHand, }); scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -831,11 +831,12 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, cx), + ContextMenu::Completions(menu) => menu.select_first(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -847,11 +848,12 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -863,11 +865,12 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, cx), + ContextMenu::Completions(menu) => menu.select_next(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -879,11 +882,12 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, cx), + ContextMenu::Completions(menu) => menu.select_last(project, style, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -928,60 +932,66 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion(project, style, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, + style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; + let language_registry = project.read(cx).languages().clone(); let completions = self.completions.clone(); let completions_guard = completions.read(); @@ -1008,16 +1018,27 @@ impl CompletionsMenu { return; } + // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { return; }; - if completion_item.documentation.is_some() { + 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 + &style, + ); + let mut completions = completions.write(); - completions[index].lsp_completion.documentation = completion_item.documentation; + let completion = &mut completions[index]; + completion.documentation = documentation; + completion.lsp_completion.documentation = Some(lsp_documentation); drop(completions); + _ = this.update(&mut cx, |_, cx| cx.notify()); } }) @@ -1069,7 +1090,7 @@ impl CompletionsMenu { let completions = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let item_ix = start_ix + ix; items.push( @@ -1100,7 +1121,9 @@ impl CompletionsMenu { ), ); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(language::Documentation::SingleLine(text)) = + documentation + { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1183,39 +1206,18 @@ impl CompletionsMenu { let mat = &self.matches[selected_item]; let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; - if let Some(lsp::Documentation::MarkupContent(content)) = documentation { - let registry = editor - .project - .as_ref() - .unwrap() - .read(cx) - .languages() - .clone(); - let language = self.buffer.read(cx).language().map(Arc::clone); + match documentation { + Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Text::new(text.clone(), style.text.clone())) + } - enum CompletionDocsMarkdown {} - Some( - Flex::column() - .scrollable::(0, None, cx) - .with_child(render_rendered_markdown( - &language::markdown::render_markdown( - &content.value, - ®istry, - &language, - &style.theme, - ), - &style, - cx, - )) - .constrained() - .with_width(alongside_docs_width) - .contained() - .with_style(alongside_docs_container_style), - ) - } else { - None + Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(render_parsed_markdown(parsed, &style, cx)) + } + + _ => None, } }) .contained() @@ -3333,7 +3335,11 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.attempt_resolve_selected_completion( + editor.project.as_ref(), + editor.style(cx).theme, + cx, + ); }); Some(menu) } @@ -5509,13 +5515,16 @@ impl Editor { return; } - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), cx)) - .unwrap_or(false) - { - return; + if self.context_menu.is_some() { + let style = self.style(cx).theme; + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) + .unwrap_or(false) + { + return; + } } if matches!(self.mode, EditorMode::SingleLine) { @@ -5555,26 +5564,30 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), cx); + context_menu.select_first(self.project.as_ref(), style, cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), cx); + context_menu.select_prev(self.project.as_ref(), style, cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), cx); + context_menu.select_next(self.project.as_ref(), style, cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { + let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), cx); + context_menu.select_last(self.project.as_ref(), style, cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index ea6eac3a66..9341fa2da3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -13,7 +13,7 @@ use gpui::{ AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, }; use language::{ - markdown::{self, RenderedRegion}, + markdown::{self, ParsedRegion}, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; @@ -367,9 +367,9 @@ fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, - language: &Option>, + language: Option>, style: &EditorStyle, -) -> RenderedInfo { +) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -382,10 +382,10 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::render_markdown_block( + HoverBlockKind::Markdown => markdown::parse_markdown_block( &block.text, language_registry, - language, + language.clone(), style, &mut text, &mut highlights, @@ -399,7 +399,7 @@ fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::render_code( + markdown::highlight_code( &mut text, &mut highlights, &block.text, @@ -413,7 +413,7 @@ fn render_blocks( } } - RenderedInfo { + ParsedInfo { theme_id, text: text.trim().to_string(), highlights, @@ -482,16 +482,16 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + rendered_content: Option, } #[derive(Debug, Clone)] -struct RenderedInfo { +struct ParsedInfo { theme_id: usize, text: String, highlights: Vec<(Range, HighlightStyle)>, region_ranges: Vec>, - regions: Vec, + regions: Vec, } impl InfoPopover { @@ -511,7 +511,7 @@ impl InfoPopover { style.theme_id, &self.blocks, self.project.read(cx).languages(), - &self.language, + self.language.clone(), style, ) }); @@ -877,7 +877,7 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1069,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), &None, &style); + let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 10585633ae..344b470aa9 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,12 +1,13 @@ pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, - markdown::RenderedMarkdown, + markdown::ParsedMarkdown, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, + markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -144,12 +145,51 @@ pub struct Diagnostic { pub is_unnecessary: bool, } +pub fn prepare_completion_documentation( + documentation: &lsp::Documentation, + language_registry: &Arc, + language: Option>, + style: &theme::Editor, +) -> Option { + match documentation { + lsp::Documentation::String(text) => { + if text.lines().count() <= 1 { + Some(Documentation::SingleLine(text.clone())) + } else { + Some(Documentation::MultiLinePlainText(text.clone())) + } + } + + lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { + lsp::MarkupKind::PlainText => { + if value.lines().count() <= 1 { + Some(Documentation::SingleLine(value.clone())) + } else { + Some(Documentation::MultiLinePlainText(value.clone())) + } + } + + lsp::MarkupKind::Markdown => { + let parsed = markdown::parse_markdown(value, language_registry, language, style); + Some(Documentation::MultiLineMarkdown(parsed)) + } + }, + } +} + +#[derive(Clone, Debug)] +pub enum Documentation { + SingleLine(String), + MultiLinePlainText(String), + MultiLineMarkdown(ParsedMarkdown), +} + #[derive(Clone, Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, - pub alongside_documentation: Option, + pub documentation: Option, pub server_id: LanguageServerId, pub lsp_completion: lsp::CompletionItem, } diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 4ccd2955b6..c56a676378 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -7,31 +7,31 @@ use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] -pub struct RenderedMarkdown { +pub struct ParsedMarkdown { pub text: String, pub highlights: Vec<(Range, HighlightStyle)>, pub region_ranges: Vec>, - pub regions: Vec, + pub regions: Vec, } #[derive(Debug, Clone)] -pub struct RenderedRegion { +pub struct ParsedRegion { pub code: bool, pub link_url: Option, } -pub fn render_markdown( +pub fn parse_markdown( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, -) -> RenderedMarkdown { +) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); let mut regions = Vec::new(); - render_markdown_block( + parse_markdown_block( markdown, language_registry, language, @@ -42,7 +42,7 @@ pub fn render_markdown( &mut regions, ); - RenderedMarkdown { + ParsedMarkdown { text, highlights, region_ranges, @@ -50,15 +50,15 @@ pub fn render_markdown( } } -pub fn render_markdown_block( +pub fn parse_markdown_block( markdown: &str, language_registry: &Arc, - language: &Option>, + language: Option>, style: &theme::Editor, text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, region_ranges: &mut Vec>, - regions: &mut Vec, + regions: &mut Vec, ) { let mut bold_depth = 0; let mut italic_depth = 0; @@ -71,7 +71,7 @@ pub fn render_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language, style); } else { text.push_str(t.as_ref()); @@ -84,7 +84,7 @@ pub fn render_markdown_block( } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); - regions.push(RenderedRegion { + regions.push(ParsedRegion { link_url: Some(link_url), code: false, }); @@ -124,7 +124,7 @@ pub fn render_markdown_block( }, )); } - regions.push(RenderedRegion { + regions.push(ParsedRegion { code: true, link_url: link_url.clone(), }); @@ -202,7 +202,7 @@ pub fn render_markdown_block( } } -pub fn render_code( +pub fn highlight_code( text: &mut String, highlights: &mut Vec<(Range, HighlightStyle)>, content: &str, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 49b332b4fb..957f4ee7fb 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -482,7 +482,7 @@ pub async fn deserialize_completion( lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, }) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 16ebb7467b..400dbe2abf 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1470,7 +1470,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - alongside_documentation: None, + documentation: None, server_id, lsp_completion, } From ea6f366d2348135897fdb4a803097d4ffdfdab24 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 3 Oct 2023 11:58:49 -0400 Subject: [PATCH 018/141] If documentation exists and hasn't been parsed, do so at render and keep --- crates/editor/src/editor.rs | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0f117cde1b..cabf73b581 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, - LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, + CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, + File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -1075,21 +1075,37 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); + let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; - let alongside_docs_width = style.autocomplete.alongside_docs_width; - let alongside_docs_container_style = style.autocomplete.alongside_docs_container; - let outer_container_style = style.autocomplete.container; - let list = UniformList::new(self.list.clone(), matches.len(), cx, { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let completions = completions.read(); + let mut completions = completions.write(); + for (ix, mat) in matches[range].iter().enumerate() { - let completion = &completions[mat.candidate_id]; + let completion = &mut completions[mat.candidate_id]; + + if completion.documentation.is_none() { + if let Some(lsp_docs) = &completion.lsp_completion.documentation { + let project = project + .as_ref() + .expect("It is impossible have LSP servers without a project"); + + let language_registry = project.read(cx).languages(); + + completion.documentation = prepare_completion_documentation( + lsp_docs, + language_registry, + None, + &style.theme, + ); + } + } + let documentation = &completion.documentation; let item_ix = start_ix + ix; @@ -1121,9 +1137,7 @@ impl CompletionsMenu { ), ); - if let Some(language::Documentation::SingleLine(text)) = - documentation - { + if let Some(Documentation::SingleLine(text)) = documentation { Flex::row() .with_child(completion_label) .with_children((|| { @@ -1209,11 +1223,11 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(language::Documentation::MultiLinePlainText(text)) => { + Some(Documentation::MultiLinePlainText(text)) => { Some(Text::new(text.clone(), style.text.clone())) } - Some(language::Documentation::MultiLineMarkdown(parsed)) => { + Some(Documentation::MultiLineMarkdown(parsed)) => { Some(render_parsed_markdown(parsed, &style, cx)) } @@ -1221,7 +1235,7 @@ impl CompletionsMenu { } }) .contained() - .with_style(outer_container_style) + .with_style(style.autocomplete.container) .into_any() } From a881b1f5fb4b2c28a48581a90e16dba1201afe7e Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 4 Oct 2023 17:36:51 -0400 Subject: [PATCH 019/141] Wait for language to load when parsing markdown --- crates/editor/src/editor.rs | 42 ++++--- crates/editor/src/hover_popover.rs | 169 +++++++++++++++++------------ crates/language/src/buffer.rs | 6 +- crates/language/src/markdown.rs | 12 +- 4 files changed, 137 insertions(+), 92 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cabf73b581..257abad41b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1018,7 +1018,6 @@ impl CompletionsMenu { return; } - // TODO: Do on background cx.spawn(|this, mut cx| async move { let request = server.request::(completion); let Some(completion_item) = request.await.log_err() else { @@ -1031,7 +1030,8 @@ impl CompletionsMenu { &language_registry, None, // TODO: Try to reasonably work out which language the completion is for &style, - ); + ) + .await; let mut completions = completions.write(); let completion = &mut completions[index]; @@ -1084,30 +1084,46 @@ impl CompletionsMenu { let style = style.clone(); move |_, range, items, cx| { let start_ix = range.start; - let mut completions = completions.write(); + let completions_guard = completions.read(); for (ix, mat) in matches[range].iter().enumerate() { - let completion = &mut completions[mat.candidate_id]; + let item_ix = start_ix + ix; + let candidate_id = mat.candidate_id; + let completion = &completions_guard[candidate_id]; - if completion.documentation.is_none() { + if item_ix == selected_item && completion.documentation.is_none() { if let Some(lsp_docs) = &completion.lsp_completion.documentation { let project = project .as_ref() .expect("It is impossible have LSP servers without a project"); - let language_registry = project.read(cx).languages(); + let lsp_docs = lsp_docs.clone(); + let lsp_docs = lsp_docs.clone(); + let language_registry = project.read(cx).languages().clone(); + let style = style.theme.clone(); + let completions = completions.clone(); - completion.documentation = prepare_completion_documentation( - lsp_docs, - language_registry, - None, - &style.theme, - ); + cx.spawn(|this, mut cx| async move { + let documentation = prepare_completion_documentation( + &lsp_docs, + &language_registry, + None, + &style, + ) + .await; + + this.update(&mut cx, |_, cx| { + let mut completions = completions.write(); + completions[candidate_id].documentation = documentation; + drop(completions); + cx.notify(); + }) + }) + .detach(); } } let documentation = &completion.documentation; - let item_ix = start_ix + ix; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9341fa2da3..585a335bd6 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, @@ -128,7 +128,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie symbol_range: DocumentRange::Inlay(inlay_hover.range), blocks: vec![inlay_hover.tooltip], language: None, - rendered_content: None, + parsed_content: None, }; this.update(&mut cx, |this, cx| { @@ -332,7 +332,7 @@ fn show_hover( symbol_range: DocumentRange::Text(range), blocks: hover_result.contents, language: hover_result.language, - rendered_content: None, + parsed_content: None, }) }); @@ -363,12 +363,12 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -fn render_blocks( +async fn render_blocks( theme_id: usize, blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &EditorStyle, + style: &theme::Editor, ) -> ParsedInfo { let mut text = String::new(); let mut highlights = Vec::new(); @@ -382,16 +382,19 @@ fn render_blocks( text.push_str(&block.text); } - HoverBlockKind::Markdown => markdown::parse_markdown_block( - &block.text, - language_registry, - language.clone(), - style, - &mut text, - &mut highlights, - &mut region_ranges, - &mut regions, - ), + HoverBlockKind::Markdown => { + markdown::parse_markdown_block( + &block.text, + language_registry, + language.clone(), + style, + &mut text, + &mut highlights, + &mut region_ranges, + &mut regions, + ) + .await + } HoverBlockKind::Code { language } => { if let Some(language) = language_registry @@ -482,7 +485,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - rendered_content: Option, + parsed_content: Option, } #[derive(Debug, Clone)] @@ -500,63 +503,87 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(rendered) = &self.rendered_content { - if rendered.theme_id != style.theme_id { - self.rendered_content = None; + if let Some(parsed) = &self.parsed_content { + if parsed.theme_id != style.theme_id { + self.parsed_content = None; } } - let rendered_content = self.rendered_content.get_or_insert_with(|| { - render_blocks( - style.theme_id, - &self.blocks, - self.project.read(cx).languages(), - self.language.clone(), - style, - ) - }); + let rendered = if let Some(parsed) = &self.parsed_content { + let view_id = cx.view_id(); + let regions = parsed.regions.clone(); + let code_span_background_color = style.document_highlight_read_background; + + let mut region_id = 0; + + Text::new(parsed.text.clone(), style.text.clone()) + .with_highlights(parsed.highlights.clone()) + .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { + region_id += 1; + let region = regions[ix].clone(); + + if let Some(url) = region.link_url { + scene.push_cursor_region(CursorRegion { + bounds, + style: CursorStyle::PointingHand, + }); + scene.push_mouse_region( + MouseRegion::new::(view_id, region_id, bounds) + .on_click::(MouseButton::Left, move |_, _, cx| { + cx.platform().open_url(&url) + }), + ); + } + + if region.code { + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); + } + }) + .with_soft_wrap(true) + .into_any() + } else { + let theme_id = style.theme_id; + let language_registry = self.project.read(cx).languages().clone(); + let blocks = self.blocks.clone(); + let language = self.language.clone(); + let style = style.theme.clone(); + cx.spawn(|this, mut cx| async move { + let blocks = + render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + _ = this.update(&mut cx, |_, cx| cx.notify()); + blocks + }) + .detach(); + + Empty::new().into_any() + }; + + // let rendered_content = self.parsed_content.get_or_insert_with(|| { + // let language_registry = self.project.read(cx).languages().clone(); + // cx.spawn(|this, mut cx| async move { + // let blocks = render_blocks( + // style.theme_id, + // &self.blocks, + // &language_registry, + // self.language.clone(), + // style, + // ) + // .await; + // this.update(&mut cx, |_, cx| cx.notify()); + // blocks + // }) + // .shared() + // }); MouseEventHandler::new::(0, cx, |_, cx| { - let mut region_id = 0; - let view_id = cx.view_id(); - - let code_span_background_color = style.document_highlight_read_background; - let regions = rendered_content.regions.clone(); Flex::column() .scrollable::(1, None, cx) - .with_child( - Text::new(rendered_content.text.clone(), style.text.clone()) - .with_highlights(rendered_content.highlights.clone()) - .with_custom_runs( - rendered_content.region_ranges.clone(), - move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::( - MouseButton::Left, - move |_, _, cx| cx.platform().open_url(&url), - ), - ); - } - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }, - ) - .with_soft_wrap(true), - ) + .with_child(rendered) .contained() .with_style(style.hover_popover.container) }) @@ -877,7 +904,8 @@ mod tests { ); let style = editor.style(cx); - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); assert_eq!( rendered.text, code_str.trim(), @@ -1069,7 +1097,8 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = render_blocks(0, &blocks, &Default::default(), None, &style); + let rendered = + smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges @@ -1339,7 +1368,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for new type hint") .text, @@ -1403,7 +1432,7 @@ mod tests { ); assert_eq!( popover - .rendered_content + .parsed_content .as_ref() .expect("should have label text for struct hint") .text, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 344b470aa9..971494ea4f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -7,7 +7,7 @@ pub use crate::{ use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, - markdown, + markdown::parse_markdown, outline::OutlineItem, syntax_map::{ SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, @@ -145,7 +145,7 @@ pub struct Diagnostic { pub is_unnecessary: bool, } -pub fn prepare_completion_documentation( +pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, @@ -170,7 +170,7 @@ pub fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = markdown::parse_markdown(value, language_registry, language, style); + let parsed = parse_markdown(value, language_registry, language, style).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index c56a676378..de5c7e8b09 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,6 @@ use std::ops::Range; use std::sync::Arc; use crate::{Language, LanguageRegistry}; -use futures::FutureExt; use gpui::fonts::{HighlightStyle, Underline, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; @@ -20,7 +19,7 @@ pub struct ParsedRegion { pub link_url: Option, } -pub fn parse_markdown( +pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, @@ -40,7 +39,8 @@ pub fn parse_markdown( &mut highlights, &mut region_ranges, &mut regions, - ); + ) + .await; ParsedMarkdown { text, @@ -50,7 +50,7 @@ pub fn parse_markdown( } } -pub fn parse_markdown_block( +pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, @@ -143,8 +143,8 @@ pub fn parse_markdown_block( current_language = if let CodeBlockKind::Fenced(language) = kind { language_registry .language_for_name(language.as_ref()) - .now_or_never() - .and_then(Result::ok) + .await + .ok() } else { language.clone() } From 8dca4c3f9ac9b923fd9bef415fb0c6de19f5becb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 14:40:41 -0400 Subject: [PATCH 020/141] Don't need editor style to parse markdown --- crates/editor/src/editor.rs | 118 ++++++++++++---------- crates/editor/src/hover_popover.rs | 154 ++++++----------------------- crates/language/src/buffer.rs | 3 +- crates/language/src/markdown.rs | 63 ++++++------ 4 files changed, 133 insertions(+), 205 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 257abad41b..6b2be7c719 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,6 +60,7 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, + markdown::MarkdownHighlight, point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, @@ -120,21 +121,57 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( - md: &language::ParsedMarkdown, - style: &EditorStyle, + parsed: &language::ParsedMarkdown, + editor_style: &EditorStyle, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} - let md = md.clone(); - let code_span_background_color = style.document_highlight_read_background; + let parsed = parsed.clone(); let view_id = cx.view_id(); + let code_span_background_color = editor_style.document_highlight_read_background; + let mut region_id = 0; - Text::new(md.text, style.text.clone()) - .with_highlights(md.highlights) - .with_custom_runs(md.region_ranges, move |ix, bounds, scene, _| { + + Text::new(parsed.text, editor_style.text.clone()) + .with_highlights( + parsed + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = match highlight { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + highlight + } + + MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, + }; + + Some((range.clone(), highlight)) + }) + .collect::>(), + ) + .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { region_id += 1; - let region = md.regions[ix].clone(); + let region = parsed.regions[ix].clone(); + if let Some(url) = region.link_url { scene.push_cursor_region(CursorRegion { bounds, @@ -147,6 +184,7 @@ pub fn render_parsed_markdown( }), ); } + if region.code { scene.push_quad(gpui::Quad { bounds, @@ -831,12 +869,11 @@ impl ContextMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_first(project, style, cx), + ContextMenu::Completions(menu) => menu.select_first(project, cx), ContextMenu::CodeActions(menu) => menu.select_first(cx), } true @@ -848,12 +885,11 @@ impl ContextMenu { fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_prev(project, style, cx), + ContextMenu::Completions(menu) => menu.select_prev(project, cx), ContextMenu::CodeActions(menu) => menu.select_prev(cx), } true @@ -865,12 +901,11 @@ impl ContextMenu { fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_next(project, style, cx), + ContextMenu::Completions(menu) => menu.select_next(project, cx), ContextMenu::CodeActions(menu) => menu.select_next(cx), } true @@ -882,12 +917,11 @@ impl ContextMenu { fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) -> bool { if self.visible() { match self { - ContextMenu::Completions(menu) => menu.select_last(project, style, cx), + ContextMenu::Completions(menu) => menu.select_last(project, cx), ContextMenu::CodeActions(menu) => menu.select_last(cx), } true @@ -932,59 +966,54 @@ impl CompletionsMenu { fn select_first( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_prev( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item > 0 { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_next( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { if self.selected_item + 1 < self.matches.len() { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn select_last( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, style, cx); + self.attempt_resolve_selected_completion(project, cx); cx.notify(); } fn attempt_resolve_selected_completion( &mut self, project: Option<&ModelHandle>, - style: theme::Editor, cx: &mut ViewContext, ) { let index = self.matches[self.selected_item].candidate_id; @@ -1029,7 +1058,6 @@ impl CompletionsMenu { &lsp_documentation, &language_registry, None, // TODO: Try to reasonably work out which language the completion is for - &style, ) .await; @@ -1097,10 +1125,8 @@ impl CompletionsMenu { .as_ref() .expect("It is impossible have LSP servers without a project"); - let lsp_docs = lsp_docs.clone(); let lsp_docs = lsp_docs.clone(); let language_registry = project.read(cx).languages().clone(); - let style = style.theme.clone(); let completions = completions.clone(); cx.spawn(|this, mut cx| async move { @@ -1108,7 +1134,6 @@ impl CompletionsMenu { &lsp_docs, &language_registry, None, - &style, ) .await; @@ -3365,11 +3390,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion( - editor.project.as_ref(), - editor.style(cx).theme, - cx, - ); + menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); }); Some(menu) } @@ -5545,16 +5566,13 @@ impl Editor { return; } - if self.context_menu.is_some() { - let style = self.style(cx).theme; - if self - .context_menu - .as_mut() - .map(|menu| menu.select_last(self.project.as_ref(), style, cx)) - .unwrap_or(false) - { - return; - } + if self + .context_menu + .as_mut() + .map(|menu| menu.select_last(self.project.as_ref(), cx)) + .unwrap_or(false) + { + return; } if matches!(self.mode, EditorMode::SingleLine) { @@ -5594,30 +5612,26 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_first(self.project.as_ref(), style, cx); + context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_prev(self.project.as_ref(), style, cx); + context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_next(self.project.as_ref(), style, cx); + context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - let style = self.style(cx).theme; if let Some(context_menu) = self.context_menu.as_mut() { - context_menu.select_last(self.project.as_ref(), style, cx); + context_menu.select_last(self.project.as_ref(), cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 585a335bd6..51fe27e58a 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -8,13 +8,11 @@ use futures::FutureExt; use gpui::{ actions, elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, - fonts::HighlightStyle, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; use language::{ - markdown::{self, ParsedRegion}, - Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, + markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, }; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; @@ -363,13 +361,11 @@ fn show_hover( editor.hover_state.info_task = Some(task); } -async fn render_blocks( - theme_id: usize, +async fn parse_blocks( blocks: &[HoverBlock], language_registry: &Arc, language: Option>, - style: &theme::Editor, -) -> ParsedInfo { +) -> markdown::ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); let mut region_ranges = Vec::new(); @@ -387,7 +383,6 @@ async fn render_blocks( &block.text, language_registry, language.clone(), - style, &mut text, &mut highlights, &mut region_ranges, @@ -402,13 +397,7 @@ async fn render_blocks( .now_or_never() .and_then(Result::ok) { - markdown::highlight_code( - &mut text, - &mut highlights, - &block.text, - &language, - style, - ); + markdown::highlight_code(&mut text, &mut highlights, &block.text, &language); } else { text.push_str(&block.text); } @@ -416,8 +405,7 @@ async fn render_blocks( } } - ParsedInfo { - theme_id, + ParsedMarkdown { text: text.trim().to_string(), highlights, region_ranges, @@ -485,16 +473,7 @@ pub struct InfoPopover { symbol_range: DocumentRange, pub blocks: Vec, language: Option>, - parsed_content: Option, -} - -#[derive(Debug, Clone)] -struct ParsedInfo { - theme_id: usize, - text: String, - highlights: Vec<(Range, HighlightStyle)>, - region_ranges: Vec>, - regions: Vec, + parsed_content: Option, } impl InfoPopover { @@ -503,58 +482,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - if let Some(parsed) = &self.parsed_content { - if parsed.theme_id != style.theme_id { - self.parsed_content = None; - } - } - let rendered = if let Some(parsed) = &self.parsed_content { - let view_id = cx.view_id(); - let regions = parsed.regions.clone(); - let code_span_background_color = style.document_highlight_read_background; - - let mut region_id = 0; - - Text::new(parsed.text.clone(), style.text.clone()) - .with_highlights(parsed.highlights.clone()) - .with_custom_runs(parsed.region_ranges.clone(), move |ix, bounds, scene, _| { - region_id += 1; - let region = regions[ix].clone(); - - if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { - bounds, - style: CursorStyle::PointingHand, - }); - scene.push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) - }), - ); - } - - if region.code { - scene.push_quad(gpui::Quad { - bounds, - background: Some(code_span_background_color), - border: Default::default(), - corner_radii: (2.0).into(), - }); - } - }) - .with_soft_wrap(true) - .into_any() + crate::render_parsed_markdown(parsed, style, cx).into_any() } else { - let theme_id = style.theme_id; let language_registry = self.project.read(cx).languages().clone(); let blocks = self.blocks.clone(); let language = self.language.clone(); - let style = style.theme.clone(); cx.spawn(|this, mut cx| async move { - let blocks = - render_blocks(theme_id, &blocks, &language_registry, language, &style).await; + let blocks = parse_blocks(&blocks, &language_registry, language).await; _ = this.update(&mut cx, |_, cx| cx.notify()); blocks }) @@ -563,23 +498,6 @@ impl InfoPopover { Empty::new().into_any() }; - // let rendered_content = self.parsed_content.get_or_insert_with(|| { - // let language_registry = self.project.read(cx).languages().clone(); - // cx.spawn(|this, mut cx| async move { - // let blocks = render_blocks( - // style.theme_id, - // &self.blocks, - // &language_registry, - // self.language.clone(), - // style, - // ) - // .await; - // this.update(&mut cx, |_, cx| cx.notify()); - // blocks - // }) - // .shared() - // }); - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) @@ -678,9 +596,12 @@ mod tests { test::editor_lsp_test_context::EditorLspTestContext, }; use collections::BTreeSet; - use gpui::fonts::{Underline, Weight}; + use gpui::fonts::Weight; use indoc::indoc; - use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; + use language::{ + language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, + DiagnosticSet, + }; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -893,7 +814,7 @@ mod tests { .await; cx.condition(|editor, _| editor.hover_state.visible()).await; - cx.editor(|editor, cx| { + cx.editor(|editor, _| { let blocks = editor.hover_state.info_popover.clone().unwrap().blocks; assert_eq!( blocks, @@ -903,9 +824,7 @@ mod tests { }], ); - let style = editor.style(cx); - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); assert_eq!( rendered.text, code_str.trim(), @@ -984,16 +903,17 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { + use markdown::MarkdownHighlight; + init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); - let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -1004,10 +924,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - weight: Some(Weight::BOLD), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + weight: Weight::BOLD, ..Default::default() - }], + })], }, // Links Row { @@ -1016,13 +936,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Lists Row { @@ -1047,13 +964,10 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, // Multi-paragraph list items Row { @@ -1081,13 +995,10 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }], + })], }, ]; @@ -1097,8 +1008,7 @@ mod tests { expected_styles, } in &rows[0..] { - let rendered = - smol::block_on(render_blocks(0, &blocks, &Default::default(), None, &style)); + let rendered = smol::block_on(parse_blocks(&blocks, &Default::default(), None)); let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false); let expected_highlights = ranges diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 971494ea4f..d318a87b40 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,7 +149,6 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> Option { match documentation { lsp::Documentation::String(text) => { @@ -170,7 +169,7 @@ pub async fn prepare_completion_documentation( } lsp::MarkupKind::Markdown => { - let parsed = parse_markdown(value, language_registry, language, style).await; + let parsed = parse_markdown(value, language_registry, language).await; Some(Documentation::MultiLineMarkdown(parsed)) } }, diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index de5c7e8b09..9f29e7cb88 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,18 +1,31 @@ use std::ops::Range; use std::sync::Arc; -use crate::{Language, LanguageRegistry}; -use gpui::fonts::{HighlightStyle, Underline, Weight}; +use crate::{HighlightId, Language, LanguageRegistry}; +use gpui::fonts::Weight; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] pub struct ParsedMarkdown { pub text: String, - pub highlights: Vec<(Range, HighlightStyle)>, + pub highlights: Vec<(Range, MarkdownHighlight)>, pub region_ranges: Vec>, pub regions: Vec, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MarkdownHighlight { + Style(MarkdownHighlightStyle), + Code(HighlightId), +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct MarkdownHighlightStyle { + pub italic: bool, + pub underline: bool, + pub weight: Weight, +} + #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, @@ -23,7 +36,6 @@ pub async fn parse_markdown( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, ) -> ParsedMarkdown { let mut text = String::new(); let mut highlights = Vec::new(); @@ -34,7 +46,6 @@ pub async fn parse_markdown( markdown, language_registry, language, - style, &mut text, &mut highlights, &mut region_ranges, @@ -54,9 +65,8 @@ pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, language: Option>, - style: &theme::Editor, text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, region_ranges: &mut Vec>, regions: &mut Vec, ) { @@ -71,16 +81,16 @@ pub async fn parse_markdown_block( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - highlight_code(text, highlights, t.as_ref(), language, style); + highlight_code(text, highlights, t.as_ref(), language); } else { text.push_str(t.as_ref()); - let mut style = HighlightStyle::default(); + let mut style = MarkdownHighlightStyle::default(); if bold_depth > 0 { - style.weight = Some(Weight::BOLD); + style.weight = Weight::BOLD; } if italic_depth > 0 { - style.italic = Some(true); + style.italic = true; } if let Some(link_url) = link_url.clone() { region_ranges.push(prev_len..text.len()); @@ -88,22 +98,22 @@ pub async fn parse_markdown_block( link_url: Some(link_url), code: false, }); - style.underline = Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }); + style.underline = true; } - if style != HighlightStyle::default() { + if style != MarkdownHighlightStyle::default() { let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { + if let Some((last_range, MarkdownHighlight::Style(last_style))) = + highlights.last_mut() + { if last_range.end == prev_len && last_style == &style { last_range.end = text.len(); new_highlight = false; } } if new_highlight { - highlights.push((prev_len..text.len(), style)); + let range = prev_len..text.len(); + highlights.push((range, MarkdownHighlight::Style(style))); } } } @@ -115,13 +125,10 @@ pub async fn parse_markdown_block( if link_url.is_some() { highlights.push(( prev_len..text.len(), - HighlightStyle { - underline: Some(Underline { - thickness: 1.0.into(), - ..Default::default() - }), + MarkdownHighlight::Style(MarkdownHighlightStyle { + underline: true, ..Default::default() - }, + }), )); } regions.push(ParsedRegion { @@ -204,17 +211,15 @@ pub async fn parse_markdown_block( pub fn highlight_code( text: &mut String, - highlights: &mut Vec<(Range, HighlightStyle)>, + highlights: &mut Vec<(Range, MarkdownHighlight)>, content: &str, language: &Arc, - style: &theme::Editor, ) { let prev_len = text.len(); text.push_str(content); for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) { - if let Some(style) = highlight_id.style(&style.syntax) { - highlights.push((prev_len + range.start..prev_len + range.end, style)); - } + let highlight = MarkdownHighlight::Code(highlight_id); + highlights.push((prev_len + range.start..prev_len + range.end, highlight)); } } From 32a29cd4d32d447c00af9157c99f45d57d219cda Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 5 Oct 2023 23:57:01 -0400 Subject: [PATCH 021/141] Unbork info popover parsing/rendering and make better --- crates/editor/src/hover_popover.rs | 105 +++++++++++++---------------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 51fe27e58a..7917d57865 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -7,7 +7,7 @@ use crate::{ use futures::FutureExt; use gpui::{ actions, - elements::{Empty, Flex, MouseEventHandler, Padding, ParentElement, Text}, + elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, }; @@ -121,12 +121,15 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie this.hover_state.diagnostic_popover = None; })?; + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = vec![inlay_hover.tooltip]; + let parsed_content = parse_blocks(&blocks, &language_registry, None).await; + let hover_popover = InfoPopover { project: project.clone(), symbol_range: DocumentRange::Inlay(inlay_hover.range), - blocks: vec![inlay_hover.tooltip], - language: None, - parsed_content: None, + blocks, + parsed_content, }; this.update(&mut cx, |this, cx| { @@ -304,35 +307,38 @@ fn show_hover( }); })?; - // Construct new hover popover from hover request - let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| { - if hover_result.is_empty() { - return None; + let hover_result = hover_request.await.ok().flatten(); + let hover_popover = match hover_result { + Some(hover_result) if !hover_result.is_empty() => { + // Create symbol range of anchors for highlighting and filtering of future requests. + let range = if let Some(range) = hover_result.range { + let start = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.start); + let end = snapshot + .buffer_snapshot + .anchor_in_excerpt(excerpt_id.clone(), range.end); + + start..end + } else { + anchor..anchor + }; + + let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); + let blocks = hover_result.contents; + let language = hover_result.language; + let parsed_content = parse_blocks(&blocks, &language_registry, language).await; + + Some(InfoPopover { + project: project.clone(), + symbol_range: DocumentRange::Text(range), + blocks, + parsed_content, + }) } - // Create symbol range of anchors for highlighting and filtering - // of future requests. - let range = if let Some(range) = hover_result.range { - let start = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.start); - let end = snapshot - .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.end); - - start..end - } else { - anchor..anchor - }; - - Some(InfoPopover { - project: project.clone(), - symbol_range: DocumentRange::Text(range), - blocks: hover_result.contents, - language: hover_result.language, - parsed_content: None, - }) - }); + _ => None, + }; this.update(&mut cx, |this, cx| { if let Some(symbol_range) = hover_popover @@ -472,8 +478,7 @@ pub struct InfoPopover { pub project: ModelHandle, symbol_range: DocumentRange, pub blocks: Vec, - language: Option>, - parsed_content: Option, + parsed_content: ParsedMarkdown, } impl InfoPopover { @@ -482,26 +487,14 @@ impl InfoPopover { style: &EditorStyle, cx: &mut ViewContext, ) -> AnyElement { - let rendered = if let Some(parsed) = &self.parsed_content { - crate::render_parsed_markdown(parsed, style, cx).into_any() - } else { - let language_registry = self.project.read(cx).languages().clone(); - let blocks = self.blocks.clone(); - let language = self.language.clone(); - cx.spawn(|this, mut cx| async move { - let blocks = parse_blocks(&blocks, &language_registry, language).await; - _ = this.update(&mut cx, |_, cx| cx.notify()); - blocks - }) - .detach(); - - Empty::new().into_any() - }; - MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() .scrollable::(1, None, cx) - .with_child(rendered) + .with_child(crate::render_parsed_markdown( + &self.parsed_content, + style, + cx, + )) .contained() .with_style(style.hover_popover.container) }) @@ -1277,11 +1270,7 @@ mod tests { "Popover range should match the new type label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for new type hint") - .text, + popover.parsed_content.text, format!("A tooltip for `{new_type_label}`"), "Rendered text should not anyhow alter backticks" ); @@ -1341,11 +1330,7 @@ mod tests { "Popover range should match the struct label part" ); assert_eq!( - popover - .parsed_content - .as_ref() - .expect("should have label text for struct hint") - .text, + popover.parsed_content.text, format!("A tooltip for {struct_label}"), "Rendered markdown element should remove backticks from text" ); From 9d8cff1275e8bfa6a6f08d48c286cdc9a8a5ab6e Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 00:17:36 -0400 Subject: [PATCH 022/141] If documentation included in original completion then parse up front --- crates/editor/src/editor.rs | 51 +++++-------------------------- crates/project/src/lsp_command.rs | 27 ++++++++++++---- 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b2be7c719..6585611040 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -61,10 +61,10 @@ pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown::MarkdownHighlight, - point_from_lsp, prepare_completion_documentation, AutoindentMode, BracketPair, Buffer, - CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, - File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, - Selection, SelectionGoal, TransactionId, + point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, + CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, + Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, + TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -940,12 +940,11 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, - editor: &Editor, style: EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(editor, style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1077,12 +1076,7 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render( - &self, - editor: &Editor, - style: EditorStyle, - cx: &mut ViewContext, - ) -> AnyElement { + fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1103,7 +1097,6 @@ impl CompletionsMenu { }) .map(|(ix, _)| ix); - let project = editor.project.clone(); let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; @@ -1118,36 +1111,6 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - - if item_ix == selected_item && completion.documentation.is_none() { - if let Some(lsp_docs) = &completion.lsp_completion.documentation { - let project = project - .as_ref() - .expect("It is impossible have LSP servers without a project"); - - let lsp_docs = lsp_docs.clone(); - let language_registry = project.read(cx).languages().clone(); - let completions = completions.clone(); - - cx.spawn(|this, mut cx| async move { - let documentation = prepare_completion_documentation( - &lsp_docs, - &language_registry, - None, - ) - .await; - - this.update(&mut cx, |_, cx| { - let mut completions = completions.write(); - completions[candidate_id].documentation = documentation; - drop(completions); - cx.notify(); - }) - }) - .detach(); - } - } - let documentation = &completion.documentation; items.push( @@ -4201,7 +4164,7 @@ impl Editor { ) -> Option<(DisplayPoint, AnyElement)> { self.context_menu .as_ref() - .map(|menu| menu.render(cursor_position, self, style, cx)) + .map(|menu| menu.render(cursor_position, style, cx)) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 400dbe2abf..c71b378da6 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -10,7 +10,7 @@ use futures::future; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ language_settings::{language_settings, InlayHintKind}, - point_from_lsp, point_to_lsp, + point_from_lsp, point_to_lsp, prepare_completion_documentation, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, @@ -1341,7 +1341,7 @@ impl LspCommand for GetCompletions { async fn response_from_lsp( self, completions: Option, - _: ModelHandle, + project: ModelHandle, buffer: ModelHandle, server_id: LanguageServerId, cx: AsyncAppContext, @@ -1361,7 +1361,8 @@ impl LspCommand for GetCompletions { Vec::new() }; - let completions = buffer.read_with(&cx, |buffer, _| { + let completions = buffer.read_with(&cx, |buffer, cx| { + let language_registry = project.read(cx).languages().clone(); let language = buffer.language().cloned(); let snapshot = buffer.snapshot(); let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); @@ -1453,14 +1454,28 @@ impl LspCommand for GetCompletions { } }; - let language = language.clone(); LineEnding::normalize(&mut new_text); + let language_registry = language_registry.clone(); + let language = language.clone(); + Some(async move { let mut label = None; - if let Some(language) = language { + if let Some(language) = language.as_ref() { language.process_completion(&mut lsp_completion).await; label = language.label_for_completion(&lsp_completion).await; } + + let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await + } else { + None + }; + Completion { old_range, new_text, @@ -1470,7 +1485,7 @@ impl LspCommand for GetCompletions { lsp_completion.filter_text.as_deref(), ) }), - documentation: None, + documentation, server_id, lsp_completion, } From f18f870206bf274a0e9a9dfd92efd8d4ed4b5c82 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 6 Oct 2023 11:56:55 -0400 Subject: [PATCH 023/141] Re-enable language servers --- crates/zed/src/languages.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3b65255a3d..04e5292a7d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -128,8 +128,8 @@ pub fn init( "tsx", tree_sitter_typescript::language_tsx(), vec![ - // Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), - // Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), + Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), ], ); From 663649a100b1a64b283bd51760179b6f49a4707e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 10:58:34 -0700 Subject: [PATCH 024/141] Fix error in query for last N channel messages --- crates/collab/src/db/queries/messages.rs | 3 +- crates/collab/src/db/tests/message_tests.rs | 67 ++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 83b5382cf5..a48d425d90 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -89,7 +89,7 @@ impl Database { let mut rows = channel_message::Entity::find() .filter(condition) - .order_by_asc(channel_message::Column::Id) + .order_by_desc(channel_message::Column::Id) .limit(count as u64) .stream(&*tx) .await?; @@ -110,6 +110,7 @@ impl Database { }); } drop(rows); + messages.reverse(); Ok(messages) }) .await diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 4966ef1bda..464aaba207 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -1,10 +1,75 @@ use crate::{ - db::{Database, NewUserParams}, + db::{Database, MessageId, NewUserParams}, test_both_dbs, }; use std::sync::Arc; use time::OffsetDateTime; +test_both_dbs!( + test_channel_message_retrieval, + test_channel_message_retrieval_postgres, + test_channel_message_retrieval_sqlite +); + +async fn test_channel_message_retrieval(db: &Arc) { + let user = db + .create_user( + "user@example.com", + false, + NewUserParams { + github_login: "user".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + let channel = db + .create_channel("channel", None, "room", user) + .await + .unwrap(); + + let owner_id = db.create_server("test").await.unwrap().0 as u32; + db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user) + .await + .unwrap(); + + let mut all_messages = Vec::new(); + for i in 0..10 { + all_messages.push( + db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i) + .await + .unwrap() + .0 + .to_proto(), + ); + } + + let messages = db + .get_channel_messages(channel, user, 3, None) + .await + .unwrap() + .into_iter() + .map(|message| message.id) + .collect::>(); + assert_eq!(messages, &all_messages[7..10]); + + let messages = db + .get_channel_messages( + channel, + user, + 4, + Some(MessageId::from_proto(all_messages[6])), + ) + .await + .unwrap() + .into_iter() + .map(|message| message.id) + .collect::>(); + assert_eq!(messages, &all_messages[2..6]); +} + test_both_dbs!( test_channel_message_nonces, test_channel_message_nonces_postgres, From 81bc86be077dbfad4402779c414d27c1f5f3d0da Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 6 Oct 2023 14:04:38 -0400 Subject: [PATCH 025/141] Use display name for release channel in panic events --- crates/zed/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 704eef464c..12ae0f2ffc 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -479,7 +479,7 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin line: location.line(), }), app_version: app_version.clone(), - release_channel: RELEASE_CHANNEL.dev_name().into(), + release_channel: RELEASE_CHANNEL.display_name().into(), os_name: platform.os_name().into(), os_version: platform .os_version() From 9f32a6e2096112a1c1097fda365047399e392115 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 11:25:46 -0700 Subject: [PATCH 026/141] collab 0.23.3 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c971846a5d..c57548057d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,7 +1467,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.23.2" +version = "0.23.3" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 0182129299..6177c23620 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.23.2" +version = "0.23.3" publish = false [[bin]] From b58c42cd536e2317a009ed48464a8f691199b731 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 13:47:35 -0600 Subject: [PATCH 027/141] TEMP --- .cargo/config.toml | 2 +- crates/util/src/channel.rs | 49 +++++---- crates/workspace/src/workspace.rs | 161 ++++++++++++++++++++---------- crates/zed/src/main.rs | 107 ++++++++++---------- crates/zed/src/open_url.rs | 7 +- 5 files changed, 197 insertions(+), 129 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 9da6b3be08..e22bdb0f2c 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 761b17e6af..2364dcaad4 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -1,7 +1,6 @@ use std::env; use lazy_static::lazy_static; -use url::Url; lazy_static! { pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) { @@ -16,22 +15,6 @@ lazy_static! { "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; - - pub static ref URL_SCHEME_PREFIX: String = match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "zed-dev:/", - "preview" => "zed-preview:/", - "stable" => "zed:/", - // NOTE: this must be kept in sync with osx_url_schemes in Cargo.toml and with https://zed.dev. - _ => unreachable!(), - }.to_string(); - pub static ref LINK_PREFIX: Url = Url::parse(match RELEASE_CHANNEL_NAME.as_str() { - "dev" => "http://localhost:3000/dev/", - "preview" => "https://zed.dev/preview/", - "stable" => "https://zed.dev/", - // NOTE: this must be kept in sync with https://zed.dev. - _ => unreachable!(), - }) - .unwrap(); } #[derive(Copy, Clone, PartialEq, Eq, Default)] @@ -58,4 +41,36 @@ impl ReleaseChannel { ReleaseChannel::Stable => "stable", } } + + pub fn url_scheme(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "zed-dev:/", + ReleaseChannel::Preview => "zed-preview:/", + ReleaseChannel::Stable => "zed:/", + } + } + + pub fn link_prefix(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "https://zed.dev/dev/", + ReleaseChannel::Preview => "https://zed.dev/preview/", + ReleaseChannel::Stable => "https://zed.dev/", + } + } +} + +pub fn parse_zed_link(link: &str) -> Option<&str> { + for release in [ + ReleaseChannel::Dev, + ReleaseChannel::Preview, + ReleaseChannel::Stable, + ] { + if let Some(stripped) = link.strip_prefix(release.link_prefix()) { + return Some(stripped); + } + if let Some(stripped) = link.strip_prefix(release.url_scheme()) { + return Some(stripped); + } + } + None } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5ec847b28b..1002ae29dc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,14 +15,14 @@ use call::ActiveCall; use channel::ChannelStore; use client::{ proto::{self, PeerId}, - Client, TypedEnvelope, UserStore, + Client, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - FutureExt, StreamExt, + select_biased, FutureExt, StreamExt, }; use gpui::{ actions, @@ -4154,6 +4154,100 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } +async fn join_channel_internal( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + active_call: &ModelHandle, + cx: &mut AsyncAppContext, +) -> Result { + let should_prompt = active_call.read_with(cx, |active_call, cx| { + let Some(room) = active_call.room().map(|room| room.read(cx)) else { + return false; + }; + + room.is_sharing_project() + && room.remote_participants().len() > 0 + && room.channel_id() != Some(channel_id) + }); + + if should_prompt { + if let Some(workspace) = requesting_window { + if let Some(window) = workspace.update(cx, |cx| cx.window()) { + let answer = window.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + cx, + ); + + if let Some(mut answer) = answer { + if answer.next().await == Some(1) { + return Ok(false); + } + } + } else { + return Ok(false); // unreachable!() hopefully + } + } else { + return Ok(false); // unreachable!() hopefully + } + } + + let client = cx.read(|cx| active_call.read(cx).client()); + + let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); + let mut client_status = client.status(); + + 'outer: loop { + select_biased! { + _ = timer => { + return Err(anyhow!("connecting timed out")) + }, + status = client_status.recv().fuse() => { + let Some(status) = status else { + return Err(anyhow!("unexpected error reading connection status")) + }; + + match status { + Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => { + if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + timer = cx.background().timer(Duration::from_secs(5)).fuse(); + } else { + return Err(anyhow!("not signed in")) + } + }, + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) + } + } + } + } + + let room = active_call + .update(cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + }) + .await?; + + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project() { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + return anyhow::Ok(true); + } + + anyhow::Ok(false) +} + pub fn join_channel( channel_id: u64, app_state: Arc, @@ -4161,50 +4255,18 @@ pub fn join_channel( cx: &mut AppContext, ) -> Task> { let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| async move { - let should_prompt = active_call.read_with(&mut cx, |active_call, cx| { - let Some(room) = active_call.room().map( |room| room.read(cx) ) else { - return false - }; + cx.spawn(|mut cx| { + let result = join_channel_internal( + channel_id, + app_state, + requesting_window, + &active_call, + &mut cx, + ) + .await; - room.is_sharing_project() && room.remote_participants().len() > 0 && - room.channel_id() != Some(channel_id) - }); - - if should_prompt { - if let Some(workspace) = requesting_window { - if let Some(window) = workspace.update(&mut cx, |cx| { - cx.window() - }) { - let answer = window.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - &mut cx, - ); - - if let Some(mut answer) = answer { - if answer.next().await == Some(1) { - return Ok(()); - } - } - } - } - } - - let room = active_call.update(&mut cx, |active_call, cx| { - active_call.join_channel(channel_id, cx) - }).await?; - - let task = room.update(&mut cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { - return Some(join_remote_project(project, host, app_state.clone(), cx)) - } - - None - }); - if let Some(task) = task { - task.await?; + // join channel succeeded, and opened a window + if Some(true) = result { return anyhow::Ok(()); } @@ -4223,16 +4285,15 @@ pub fn join_channel( }); if found.unwrap_or(false) { - return anyhow::Ok(()) + return anyhow::Ok(()); } } // no open workspaces - cx.update(|cx| { - Workspace::new_local(vec![], app_state.clone(), requesting_window, cx) - }).await; + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; - return anyhow::Ok(()); + return connected.map(|_| ()); }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9d0451ecfa..861121a1cf 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,7 @@ use std::{ }; use sum_tree::Bias; use util::{ - channel::ReleaseChannel, + channel::{parse_zed_link, ReleaseChannel}, http::{self, HttpClient}, paths::PathLikeWithPosition, }; @@ -206,12 +206,9 @@ fn main() { if stdout_is_a_pty() { cx.platform().activate(true); - let paths = collect_path_args(); - if paths.is_empty() { - cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await }) - .detach() - } else { - workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx); + let urls = collect_url_args(); + if !urls.is_empty() { + listener.open_urls(urls) } } else { upload_previous_panics(http.clone(), cx); @@ -223,53 +220,51 @@ fn main() { { listener.open_urls(collect_url_args()) } + } - match open_rx.try_next() { - Ok(Some(OpenRequest::Paths { paths })) => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - Ok(Some(OpenRequest::CliConnection { connection })) => { - cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) - .detach(); - } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach(), - Ok(None) | Err(_) => cx - .spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(&app_state, cx).await } - }) - .detach(), + match open_rx.try_next() { + Ok(Some(OpenRequest::Paths { paths })) => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) + .detach(); } + Ok(Some(OpenRequest::CliConnection { connection })) => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } + Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx + .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) + .detach_and_log_err(cx), + Ok(None) | Err(_) => cx + .spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(&app_state, cx).await } + }) + .detach(), + } - cx.spawn(|mut cx| { - let app_state = app_state.clone(); - async move { - while let Some(request) = open_rx.next().await { - match request { - OpenRequest::Paths { paths } => { - cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) - .detach(); - } - OpenRequest::CliConnection { connection } => { - cx.spawn(|cx| { - handle_cli_connection(connection, app_state.clone(), cx) - }) + cx.spawn(|mut cx| { + let app_state = app_state.clone(); + async move { + while let Some(request) = open_rx.next().await { + match request { + OpenRequest::Paths { paths } => { + cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) .detach(); - } - OpenRequest::JoinChannel { channel_id } => cx - .update(|cx| { - workspace::join_channel(channel_id, app_state.clone(), None, cx) - }) - .detach(), } + OpenRequest::CliConnection { connection } => { + cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) + .detach(); + } + OpenRequest::JoinChannel { channel_id } => cx + .update(|cx| { + workspace::join_channel(channel_id, app_state.clone(), None, cx) + }) + .detach(), } } - }) - .detach(); - } + } + }) + .detach(); cx.spawn(|cx| async move { if stdout_is_a_pty() { @@ -608,23 +603,23 @@ fn stdout_is_a_pty() -> bool { std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal() } -fn collect_path_args() -> Vec { +fn collect_url_args() -> Vec { env::args() .skip(1) - .filter_map(|arg| match std::fs::canonicalize(arg) { - Ok(path) => Some(path), + .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) { + Ok(path) => Some(format!("file://{}", path.to_string_lossy())), Err(error) => { - log::error!("error parsing path argument: {}", error); - None + if let Some(_) = parse_zed_link(&arg) { + Some(arg) + } else { + log::error!("error parsing path argument: {}", error); + None + } } }) .collect() } -fn collect_url_args() -> Vec { - env::args().skip(1).collect() -} - fn load_embedded_fonts(app: &App) { let font_paths = Assets.list("fonts"); let embedded_fonts = Mutex::new(Vec::new()); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 1c741a02c8..6f90953de2 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -6,7 +6,7 @@ use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; -use util::channel::URL_SCHEME_PREFIX; +use util::channel::parse_zed_link; use util::ResultExt; use crate::connect_to_cli; @@ -47,10 +47,7 @@ impl OpenListener { urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { self.handle_cli_connection(server_name) - } else if let Some(request_path) = urls - .first() - .and_then(|url| url.strip_prefix(URL_SCHEME_PREFIX.as_str())) - { + } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url)) { self.handle_zed_url_scheme(request_path) } else { self.handle_file_urls(urls) From 43da36948bdb0a230823f173d9a3e339e0ce2811 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 13:14:39 -0700 Subject: [PATCH 028/141] Add a crate-dep-graph script for showing the crate dependency graph --- script/crate-dep-graph | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 script/crate-dep-graph diff --git a/script/crate-dep-graph b/script/crate-dep-graph new file mode 100755 index 0000000000..25285cc097 --- /dev/null +++ b/script/crate-dep-graph @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +if [[ -x cargo-depgraph ]]; then + cargo install cargo-depgraph +fi + +graph_file=target/crate-graph.html + +cargo depgraph \ + --workspace-only \ + --offline \ + --root=zed,cli,collab \ + --dedup-transitive-deps \ + | dot -Tsvg > $graph_file + +echo "open $graph_file" +open $graph_file From 17925ed5631f86a8564c885172cd9521012b2edc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 13:14:53 -0700 Subject: [PATCH 029/141] Remove unnecessary dependencies on client and rpc --- Cargo.lock | 1 - crates/fs/Cargo.toml | 1 - crates/fs/src/repository.rs | 20 +------------------- crates/language/Cargo.toml | 1 - crates/project/src/worktree.rs | 22 ++++++++++++++++++++-- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c57548057d..1b6583f067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2832,7 +2832,6 @@ dependencies = [ "parking_lot 0.11.2", "regex", "rope", - "rpc", "serde", "serde_derive", "serde_json", diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 441ce6f9c7..11a34bcecb 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -13,7 +13,6 @@ rope = { path = "../rope" } text = { path = "../text" } util = { path = "../util" } sum_tree = { path = "../sum_tree" } -rpc = { path = "../rpc" } anyhow.workspace = true async-trait.workspace = true diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 2b2aebe679..4637a7f754 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -2,7 +2,6 @@ use anyhow::Result; use collections::HashMap; use git2::{BranchType, StatusShow}; use parking_lot::Mutex; -use rpc::proto; use serde_derive::{Deserialize, Serialize}; use std::{ cmp::Ordering, @@ -23,6 +22,7 @@ pub struct Branch { /// Timestamp of most recent commit, normalized to Unix Epoch format. pub unix_timestamp: Option, } + #[async_trait::async_trait] pub trait GitRepository: Send { fn reload_index(&self); @@ -358,24 +358,6 @@ impl GitFileStatus { } } } - - pub fn from_proto(git_status: Option) -> Option { - git_status.and_then(|status| { - proto::GitStatus::from_i32(status).map(|status| match status { - proto::GitStatus::Added => GitFileStatus::Added, - proto::GitStatus::Modified => GitFileStatus::Modified, - proto::GitStatus::Conflict => GitFileStatus::Conflict, - }) - }) - } - - pub fn to_proto(self) -> i32 { - match self { - GitFileStatus::Added => proto::GitStatus::Added as i32, - GitFileStatus::Modified => proto::GitStatus::Modified as i32, - GitFileStatus::Conflict => proto::GitStatus::Conflict as i32, - } - } } #[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4771fc7083..cf468020ce 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -22,7 +22,6 @@ test-support = [ ] [dependencies] -client = { path = "../client" } clock = { path = "../clock" } collections = { path = "../collections" } fuzzy = { path = "../fuzzy" } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2de3671033..a38e43cd87 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4310,7 +4310,7 @@ impl<'a> From<&'a Entry> for proto::Entry { is_symlink: entry.is_symlink, is_ignored: entry.is_ignored, is_external: entry.is_external, - git_status: entry.git_status.map(|status| status.to_proto()), + git_status: entry.git_status.map(git_status_to_proto), } } } @@ -4337,7 +4337,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { is_symlink: entry.is_symlink, is_ignored: entry.is_ignored, is_external: entry.is_external, - git_status: GitFileStatus::from_proto(entry.git_status), + git_status: git_status_from_proto(entry.git_status), }) } else { Err(anyhow!( @@ -4366,3 +4366,21 @@ fn combine_git_statuses( unstaged } } + +fn git_status_from_proto(git_status: Option) -> Option { + git_status.and_then(|status| { + proto::GitStatus::from_i32(status).map(|status| match status { + proto::GitStatus::Added => GitFileStatus::Added, + proto::GitStatus::Modified => GitFileStatus::Modified, + proto::GitStatus::Conflict => GitFileStatus::Conflict, + }) + }) +} + +fn git_status_to_proto(status: GitFileStatus) -> i32 { + match status { + GitFileStatus::Added => proto::GitStatus::Added as i32, + GitFileStatus::Modified => proto::GitStatus::Modified as i32, + GitFileStatus::Conflict => proto::GitStatus::Conflict as i32, + } +} From 3412bb75bebcc8ec4f77234baa9acca7fb1bc48f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 13:39:10 -0700 Subject: [PATCH 030/141] Remove call -> channel dependency --- Cargo.lock | 1 - crates/call/Cargo.toml | 1 - crates/call/src/call.rs | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b6583f067..60d7b0b7d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,7 +1082,6 @@ dependencies = [ "anyhow", "async-broadcast", "audio", - "channel", "client", "collections", "fs", diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index b4e94fe56c..eb448d8d8d 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -20,7 +20,6 @@ test-support = [ [dependencies] audio = { path = "../audio" } -channel = { path = "../channel" } client = { path = "../client" } collections = { path = "../collections" } gpui = { path = "../gpui" } diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index d86ed1be37..0846341325 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -5,7 +5,6 @@ pub mod room; use anyhow::{anyhow, Result}; use audio::Audio; use call_settings::CallSettings; -use channel::ChannelId; use client::{ proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, @@ -79,7 +78,7 @@ impl ActiveCall { } } - pub fn channel_id(&self, cx: &AppContext) -> Option { + pub fn channel_id(&self, cx: &AppContext) -> Option { self.room()?.read(cx).channel_id() } From 4128e2ffcbb5317ffc668c0c851e3a51474649e2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 15:18:25 -0600 Subject: [PATCH 031/141] Fix panic if the host is not there. --- crates/call/src/room.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 72db174d72..2543697bc0 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -602,6 +602,7 @@ impl Room { pub fn most_active_project(&self) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); + for participant in self.remote_participants.values() { match participant.location { ParticipantLocation::SharedProject { project_id } => { @@ -619,8 +620,8 @@ impl Room { pairs.sort_by_key(|(_, count)| *count as i32); pairs - .first() - .map(|(project_id, _)| (*project_id, hosts[&project_id])) + .iter() + .find_map(|(project_id, _)| hosts.get(project_id).map(|host| (*project_id, *host))) } async fn handle_room_updated( From f8ca86c6a7d32f45293c6837fa23bf5bf4a6b060 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Oct 2023 14:16:08 -0700 Subject: [PATCH 032/141] Remove workspace -> channel dependency --- Cargo.lock | 1 - crates/channel/src/channel.rs | 10 +++--- crates/channel/src/channel_store.rs | 11 ++++++- crates/channel/src/channel_store_tests.rs | 4 +-- crates/collab/src/tests/test_server.rs | 39 ++++++++++------------- crates/collab_ui/src/channel_view.rs | 2 +- crates/collab_ui/src/chat_panel.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 2 +- crates/workspace/Cargo.toml | 1 - crates/workspace/src/workspace.rs | 9 +----- crates/zed/src/main.rs | 6 +--- crates/zed/src/zed.rs | 1 + 12 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60d7b0b7d5..24d8359421 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9969,7 +9969,6 @@ dependencies = [ "async-recursion 1.0.5", "bincode", "call", - "channel", "client", "collections", "context_menu", diff --git a/crates/channel/src/channel.rs b/crates/channel/src/channel.rs index 160b8441ff..d31d4b3c8c 100644 --- a/crates/channel/src/channel.rs +++ b/crates/channel/src/channel.rs @@ -2,19 +2,21 @@ mod channel_buffer; mod channel_chat; mod channel_store; +use client::{Client, UserStore}; +use gpui::{AppContext, ModelHandle}; +use std::sync::Arc; + pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL}; pub use channel_chat::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId}; pub use channel_store::{ Channel, ChannelData, ChannelEvent, ChannelId, ChannelMembership, ChannelPath, ChannelStore, }; -use client::Client; -use std::sync::Arc; - #[cfg(test)] mod channel_store_tests; -pub fn init(client: &Arc) { +pub fn init(client: &Arc, user_store: ModelHandle, cx: &mut AppContext) { + channel_store::init(client, user_store, cx); channel_buffer::init(client); channel_chat::init(client); } diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index bd72c92c7d..8cdd11b4ec 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -2,6 +2,7 @@ mod channel_index; use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat}; use anyhow::{anyhow, Result}; +use channel_index::ChannelIndex; use client::{Client, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; @@ -14,7 +15,11 @@ use serde_derive::{Deserialize, Serialize}; use std::{borrow::Cow, hash::Hash, mem, ops::Deref, sync::Arc, time::Duration}; use util::ResultExt; -use self::channel_index::ChannelIndex; +pub fn init(client: &Arc, user_store: ModelHandle, cx: &mut AppContext) { + let channel_store = + cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); + cx.set_global(channel_store); +} pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -71,6 +76,10 @@ enum OpenedModelHandle { } impl ChannelStore { + pub fn global(cx: &AppContext) -> ModelHandle { + cx.global::>().clone() + } + pub fn new( client: Arc, user_store: ModelHandle, diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 41acafa3a3..9303a52092 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -340,10 +340,10 @@ fn init_test(cx: &mut AppContext) -> ModelHandle { cx.foreground().forbid_parking(); cx.set_global(SettingsStore::test(cx)); - crate::init(&client); client::init(&client, cx); + crate::init(&client, user_store, cx); - cx.add_model(|cx| ChannelStore::new(client, user_store, cx)) + ChannelStore::global(cx) } fn update_channels( diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index e10ded7d95..2e13874125 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -44,6 +44,7 @@ pub struct TestServer { pub struct TestClient { pub username: String, pub app_state: Arc, + channel_store: ModelHandle, state: RefCell, } @@ -206,15 +207,12 @@ impl TestServer { let fs = FakeFs::new(cx.background()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); - let channel_store = - cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); let mut language_registry = LanguageRegistry::test(); language_registry.set_executor(cx.background()); let app_state = Arc::new(workspace::AppState { client: client.clone(), user_store: user_store.clone(), workspace_store, - channel_store: channel_store.clone(), languages: Arc::new(language_registry), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), @@ -231,7 +229,7 @@ impl TestServer { workspace::init(app_state.clone(), cx); audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); - channel::init(&client); + channel::init(&client, user_store, cx); }); client @@ -242,6 +240,7 @@ impl TestServer { let client = TestClient { app_state, username: name.to_string(), + channel_store: cx.read(ChannelStore::global).clone(), state: Default::default(), }; client.wait_for_current_user(cx).await; @@ -310,10 +309,9 @@ impl TestServer { admin: (&TestClient, &mut TestAppContext), members: &mut [(&TestClient, &mut TestAppContext)], ) -> u64 { - let (admin_client, admin_cx) = admin; - let channel_id = admin_client - .app_state - .channel_store + let (_, admin_cx) = admin; + let channel_id = admin_cx + .read(ChannelStore::global) .update(admin_cx, |channel_store, cx| { channel_store.create_channel(channel, parent, cx) }) @@ -321,9 +319,8 @@ impl TestServer { .unwrap(); for (member_client, member_cx) in members { - admin_client - .app_state - .channel_store + admin_cx + .read(ChannelStore::global) .update(admin_cx, |channel_store, cx| { channel_store.invite_member( channel_id, @@ -337,9 +334,8 @@ impl TestServer { admin_cx.foreground().run_until_parked(); - member_client - .app_state - .channel_store + member_cx + .read(ChannelStore::global) .update(*member_cx, |channels, _| { channels.respond_to_channel_invite(channel_id, true) }) @@ -447,7 +443,7 @@ impl TestClient { } pub fn channel_store(&self) -> &ModelHandle { - &self.app_state.channel_store + &self.channel_store } pub fn user_store(&self) -> &ModelHandle { @@ -614,8 +610,8 @@ impl TestClient { ) { let (other_client, other_cx) = user; - self.app_state - .channel_store + cx_self + .read(ChannelStore::global) .update(cx_self, |channel_store, cx| { channel_store.invite_member(channel, other_client.user_id().unwrap(), true, cx) }) @@ -624,11 +620,10 @@ impl TestClient { cx_self.foreground().run_until_parked(); - other_client - .app_state - .channel_store - .update(other_cx, |channels, _| { - channels.respond_to_channel_invite(channel, true) + other_cx + .read(ChannelStore::global) + .update(other_cx, |channel_store, _| { + channel_store.respond_to_channel_invite(channel, true) }) .await .unwrap(); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index a955768050..b2e65eb2fa 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -73,7 +73,7 @@ impl ChannelView { ) -> Task>> { let workspace = workspace.read(cx); let project = workspace.project().to_owned(); - let channel_store = workspace.app_state().channel_store.clone(); + let channel_store = ChannelStore::global(cx); let markdown = workspace .app_state() .languages diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index b446521c5a..1a17b48f19 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -81,7 +81,7 @@ impl ChatPanel { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> ViewHandle { let fs = workspace.app_state().fs.clone(); let client = workspace.app_state().client.clone(); - let channel_store = workspace.app_state().channel_store.clone(); + let channel_store = ChannelStore::global(cx); let languages = workspace.app_state().languages.clone(); let input_editor = cx.add_view(|cx| { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 951c8bf70c..9e09abbd6a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -648,7 +648,7 @@ impl CollabPanel { channel_editing_state: None, selection: None, user_store: workspace.user_store().clone(), - channel_store: workspace.app_state().channel_store.clone(), + channel_store: ChannelStore::global(cx), project: workspace.project().clone(), subscriptions: Vec::default(), match_candidates: Vec::default(), diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 41c86e538d..d1240a45ce 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -22,7 +22,6 @@ test-support = [ db = { path = "../db" } call = { path = "../call" } client = { path = "../client" } -channel = { path = "../channel" } collections = { path = "../collections" } context_menu = { path = "../context_menu" } drag_and_drop = { path = "../drag_and_drop" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6496d81349..d39c1cce76 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -12,7 +12,6 @@ mod workspace_settings; use anyhow::{anyhow, Context, Result}; use call::ActiveCall; -use channel::ChannelStore; use client::{ proto::{self, PeerId}, Client, TypedEnvelope, UserStore, @@ -450,7 +449,6 @@ pub struct AppState { pub languages: Arc, pub client: Arc, pub user_store: ModelHandle, - pub channel_store: ModelHandle, pub workspace_store: ModelHandle, pub fs: Arc, pub build_window_options: @@ -487,8 +485,6 @@ impl AppState { let http_client = util::http::FakeHttpClient::with_404_response(); let client = Client::new(http_client.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let channel_store = - cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); theme::init((), cx); @@ -500,7 +496,7 @@ impl AppState { fs, languages, user_store, - channel_store, + // channel_store, workspace_store, initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), @@ -3527,15 +3523,12 @@ impl Workspace { let client = project.read(cx).client(); let user_store = project.read(cx).user_store(); - let channel_store = - cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); let app_state = Arc::new(AppState { languages: project.read(cx).languages().clone(), workspace_store, client, user_store, - channel_store, fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 12ae0f2ffc..6c8d321850 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -3,7 +3,6 @@ use anyhow::{anyhow, Context, Result}; use backtrace::Backtrace; -use channel::ChannelStore; use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, @@ -138,8 +137,6 @@ fn main() { languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let channel_store = - cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx)); cx.set_global(client.clone()); @@ -156,7 +153,7 @@ fn main() { outline::init(cx); project_symbols::init(cx); project_panel::init(Assets, cx); - channel::init(&client); + channel::init(&client, user_store.clone(), cx); diagnostics::init(cx); search::init(cx); semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); @@ -184,7 +181,6 @@ fn main() { languages, client: client.clone(), user_store, - channel_store, fs, build_window_options, initialize_workspace, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ce9b7e32a3..4e9a34c269 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2424,6 +2424,7 @@ mod tests { state.build_window_options = build_window_options; theme::init((), cx); audio::init((), cx); + channel::init(&app_state.client, app_state.user_store.clone(), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); Project::init_settings(cx); From 63a230f92e04e839f52855419dfb7e3477910866 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:11:45 -0600 Subject: [PATCH 033/141] Make joining on boot work --- crates/call/src/room.rs | 20 +++++- crates/client/src/client.rs | 3 +- crates/workspace/src/workspace.rs | 110 +++++++++++++++++------------- crates/zed/src/main.rs | 49 ++++++++----- crates/zed/src/open_url.rs | 1 - 5 files changed, 118 insertions(+), 65 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 2543697bc0..5419e00f02 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -18,7 +18,7 @@ use live_kit_client::{ LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, }; -use postage::stream::Stream; +use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -70,6 +70,8 @@ pub struct Room { user_store: ModelHandle, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec>, subscriptions: Vec, + room_update_completed_tx: watch::Sender>, + room_update_completed_rx: watch::Receiver>, pending_room_update: Option>, maintain_connection: Option>>, } @@ -211,6 +213,8 @@ impl Room { Audio::play_sound(Sound::Joined, cx); + let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); + Self { id, channel_id, @@ -230,6 +234,8 @@ impl Room { user_store, follows_by_leader_id_project_id: Default::default(), maintain_connection: Some(maintain_connection), + room_update_completed_tx, + room_update_completed_rx, } } @@ -856,6 +862,7 @@ impl Room { }); this.check_invariants(); + this.room_update_completed_tx.try_send(Some(())).ok(); cx.notify(); }); })); @@ -864,6 +871,17 @@ impl Room { Ok(()) } + pub fn next_room_update(&mut self) -> impl Future { + let mut done_rx = self.room_update_completed_rx.clone(); + async move { + while let Some(result) = done_rx.next().await { + if result.is_some() { + break; + } + } + } + } + fn remote_video_track_updated( &mut self, change: RemoteVideoTrackUpdate, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 5767ac54b7..62cd60c55f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,8 @@ impl Client { } } } - _ = status_rx.next().fuse() => { + status = status_rx.next().fuse() => { + dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1002ae29dc..a124917309 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -22,7 +22,7 @@ use drag_and_drop::DragAndDrop; use futures::{ channel::{mpsc, oneshot}, future::try_join_all, - select_biased, FutureExt, StreamExt, + FutureExt, StreamExt, }; use gpui::{ actions, @@ -36,9 +36,9 @@ use gpui::{ CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions, }, - AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, - ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, WindowHandle, + AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext, + Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, WindowHandle, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; @@ -4156,7 +4156,7 @@ pub async fn last_opened_workspace_paths() -> Option { async fn join_channel_internal( channel_id: u64, - app_state: Arc, + app_state: &Arc, requesting_window: Option>, active_call: &ModelHandle, cx: &mut AsyncAppContext, @@ -4196,33 +4196,24 @@ async fn join_channel_internal( let client = cx.read(|cx| active_call.read(cx).client()); - let mut timer = cx.background().timer(Duration::from_secs(5)).fuse(); let mut client_status = client.status(); + // this loop will terminate within client::CONNECTION_TIMEOUT seconds. 'outer: loop { - select_biased! { - _ = timer => { - return Err(anyhow!("connecting timed out")) - }, - status = client_status.recv().fuse() => { - let Some(status) = status else { - return Err(anyhow!("unexpected error reading connection status")) - }; + let Some(status) = client_status.recv().await else { + return Err(anyhow!("error connecting")); + }; - match status { - Status::Connecting | Status::Authenticating | Status::Reconnecting | Status::Reauthenticating => continue, - Status::Connected { .. } => break 'outer, - Status::SignedOut => { - if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - timer = cx.background().timer(Duration::from_secs(5)).fuse(); - } else { - return Err(anyhow!("not signed in")) - } - }, - Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), - | Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => return Err(anyhow!("zed is offline")) - } + match status { + Status::Connecting + | Status::Authenticating + | Status::Reconnecting + | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => return Err(anyhow!("not signed in")), + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { + return Err(anyhow!("zed is offline")) } } } @@ -4233,6 +4224,8 @@ async fn join_channel_internal( }) .await?; + room.update(cx, |room, cx| room.next_room_update()).await; + let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4255,10 +4248,10 @@ pub fn join_channel( cx: &mut AppContext, ) -> Task> { let active_call = ActiveCall::global(cx); - cx.spawn(|mut cx| { + cx.spawn(|mut cx| async move { let result = join_channel_internal( channel_id, - app_state, + &app_state, requesting_window, &active_call, &mut cx, @@ -4266,7 +4259,7 @@ pub fn join_channel( .await; // join channel succeeded, and opened a window - if Some(true) = result { + if matches!(result, Ok(true)) { return anyhow::Ok(()); } @@ -4275,28 +4268,53 @@ pub fn join_channel( } // find an existing workspace to focus and show call controls - for window in cx.windows() { - let found = window.update(&mut cx, |cx| { - let is_workspace = cx.root_view().clone().downcast::().is_some(); - if is_workspace { - cx.activate_window(); - } - is_workspace - }); + let mut active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + // no open workspaces, make one to show the error in (blergh) + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) + .await; + } - if found.unwrap_or(false) { - return anyhow::Ok(()); + active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + return result.map(|_| ()); // unreachable!() assuming new_local always opens a window + } + + if let Err(err) = result { + let prompt = active_window.unwrap().prompt( + PromptLevel::Critical, + &format!("Failed to join channel: {}", err), + &["Ok"], + &mut cx, + ); + if let Some(mut prompt) = prompt { + prompt.next().await; + } else { + return Err(err); } } - // no open workspaces - cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) - .await; - - return connected.map(|_| ()); + // return ok, we showed the error to the user. + return anyhow::Ok(()); }) } +pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { + for window in cx.windows() { + let found = window.update(cx, |cx| { + let is_workspace = cx.root_view().clone().downcast::().is_some(); + if is_workspace { + cx.activate_window(); + } + is_workspace + }); + if found == Some(true) { + return Some(window); + } + } + None +} + #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 861121a1cf..52aaf639ea 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,7 +8,9 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; -use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{ + self, Client, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN, +}; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Editor}; use futures::{ @@ -33,7 +35,7 @@ use std::{ fs::OpenOptions, io::{IsTerminal, Write as _}, panic, - path::{Path, PathBuf}, + path::Path, sync::{ atomic::{AtomicU32, Ordering}, Arc, Weak, @@ -222,6 +224,8 @@ fn main() { } } + let mut triggered_authentication = false; + match open_rx.try_next() { Ok(Some(OpenRequest::Paths { paths })) => { cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) @@ -231,9 +235,18 @@ fn main() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); } - Ok(Some(OpenRequest::JoinChannel { channel_id })) => cx - .update(|cx| workspace::join_channel(channel_id, app_state.clone(), None, cx)) - .detach_and_log_err(cx), + Ok(Some(OpenRequest::JoinChannel { channel_id })) => { + triggered_authentication = true; + let app_state = app_state.clone(); + let client = client.clone(); + cx.spawn(|mut cx| async move { + // ignore errors here, we'll show a generic "not signed in" + let _ = authenticate(client, &cx).await; + cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx)) + .await + }) + .detach_and_log_err(cx) + } Ok(None) | Err(_) => cx .spawn({ let app_state = app_state.clone(); @@ -266,20 +279,24 @@ fn main() { }) .detach(); - cx.spawn(|cx| async move { - if stdout_is_a_pty() { - if client::IMPERSONATE_LOGIN.is_some() { - client.authenticate_and_connect(false, &cx).await?; - } - } else if client.has_keychain_credentials(&cx) { - client.authenticate_and_connect(true, &cx).await?; - } - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); + if !triggered_authentication { + cx.spawn(|cx| async move { authenticate(client, &cx).await }) + .detach_and_log_err(cx); + } }); } +async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { + if stdout_is_a_pty() { + if client::IMPERSONATE_LOGIN.is_some() { + client.authenticate_and_connect(false, &cx).await?; + } + } else if client.has_keychain_credentials(&cx) { + client.authenticate_and_connect(true, &cx).await?; + } + Ok::<_, anyhow::Error>(()) +} + async fn installation_id() -> Result { let legacy_key_name = "device_id"; diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 6f90953de2..3e4902b978 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -42,7 +42,6 @@ impl OpenListener { pub fn open_urls(&self, urls: Vec) { self.triggered.store(true, Ordering::Release); - dbg!(&urls); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { From f6bc229d1d4f4d46139ec268241fb69d925fceed Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 16:48:29 -0600 Subject: [PATCH 034/141] More progress and some debug logs to remove --- assets/settings/default.json | 2 +- crates/util/src/channel.rs | 6 +++--- crates/workspace/src/workspace.rs | 21 +++++++++++++++++++-- crates/zed/src/main.rs | 10 ++++++++++ crates/zed/src/open_url.rs | 13 +++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb..cc724657c0 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -76,7 +76,7 @@ // Settings related to calls in Zed "calls": { // Join calls with the microphone muted by default - "mute_on_join": true + "mute_on_join": false }, // Scrollbar related settings "scrollbar": { diff --git a/crates/util/src/channel.rs b/crates/util/src/channel.rs index 2364dcaad4..47c6a570a1 100644 --- a/crates/util/src/channel.rs +++ b/crates/util/src/channel.rs @@ -44,9 +44,9 @@ impl ReleaseChannel { pub fn url_scheme(&self) -> &'static str { match self { - ReleaseChannel::Dev => "zed-dev:/", - ReleaseChannel::Preview => "zed-preview:/", - ReleaseChannel::Stable => "zed:/", + ReleaseChannel::Dev => "zed-dev://", + ReleaseChannel::Preview => "zed-preview://", + ReleaseChannel::Stable => "zed://", } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a124917309..1644f86b61 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,6 +47,7 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, + fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4161,6 +4162,7 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { + dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4193,6 +4195,7 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } + dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4218,13 +4221,17 @@ async fn join_channel_internal( } } + dbg!("past here"); + let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) }) .await?; - room.update(cx, |room, cx| room.next_room_update()).await; + room.update(cx, |room, _| room.next_room_update()).await; + + dbg!("wow"); let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { @@ -4237,7 +4244,16 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - + use std::io::Write; + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "no jokes" + ) + .unwrap(); anyhow::Ok(false) } @@ -4257,6 +4273,7 @@ pub fn join_channel( &mut cx, ) .await; + dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 52aaf639ea..f25a1e14be 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -66,6 +66,15 @@ use crate::open_url::{OpenListener, OpenRequest}; mod open_url; fn main() { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "HELLO" + ) + .unwrap(); let http = http::client(); init_paths(); init_logger(); @@ -270,6 +279,7 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { + dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_url.rs index 3e4902b978..8cad217a8c 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_url.rs @@ -3,6 +3,8 @@ use cli::{ipc::IpcSender, CliRequest, CliResponse}; use futures::channel::mpsc; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::ffi::OsStr; +use std::fs::OpenOptions; +use std::io::Write; use std::os::unix::prelude::OsStrExt; use std::sync::atomic::Ordering; use std::{path::PathBuf, sync::atomic::AtomicBool}; @@ -41,6 +43,16 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { + writeln!( + OpenOptions::new() + .write(true) + .append(true) + .open("/Users/conrad/dbg") + .unwrap(), + "{:?}", + &urls, + ) + .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) @@ -79,6 +91,7 @@ impl OpenListener { } } } + log::error!("invalid zed url: {}", request_path); None } From 6de69de868fcee8214edca76b8a09d2210885646 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 6 Oct 2023 16:04:45 -0700 Subject: [PATCH 035/141] Remove change to linker args --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c..9da6b3be08 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] From 66120fb97a3dca9292a3c978152246f675198adb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 22:24:41 -0600 Subject: [PATCH 036/141] Try universal link entitlement too --- crates/zed/resources/zed.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index dca0ff6766..9f5414243d 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.associated-domains + applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit From 34b75379485f82f531cba5325997a5e42600a379 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 6 Oct 2023 23:15:37 -0600 Subject: [PATCH 037/141] Add universal links support to mac platform --- crates/gpui/src/platform/mac/platform.rs | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 656f2e4475..47ad72eeb2 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -140,6 +140,10 @@ unsafe fn build_classes() { sel!(application:openURLs:), open_urls as extern "C" fn(&mut Object, Sel, id, id), ); + decl.add_method( + sel!(application:continueUserActivity:restorationHandler:), + continue_user_activity as extern "C" fn(&mut Object, Sel, id, id, id), + ); decl.register() } } @@ -1009,6 +1013,28 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } } +extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { + dbg!("yay!"); + let url = unsafe { + let url: id = msg_send!(user_activity, webpageURL); + if url == nil { + log::error!("got unexpected user activity"); + None + } else { + Some( + CStr::from_ptr(url.absoluteString().UTF8String()) + .to_string_lossy() + .to_string(), + ) + } + }; + dbg!(&url); + let platform = unsafe { get_foreground_platform(this) }; + if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { + callback(url.into_iter().collect()); + } +} + extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_foreground_platform(this); From 4b2c24dd8c9836deb973935a46db70cdff3c74b6 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sun, 8 Oct 2023 20:07:59 -0400 Subject: [PATCH 038/141] Add enable vim mode checkbox to welcome screen --- Cargo.lock | 1 + crates/vim/src/vim.rs | 2 +- crates/welcome/Cargo.toml | 1 + crates/welcome/src/welcome.rs | 25 ++++++++++++++++++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c57548057d..3c7affc76f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9665,6 +9665,7 @@ dependencies = [ "theme", "theme_selector", "util", + "vim", "workspace", ] diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index d27be2c54b..aad97c558e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -33,7 +33,7 @@ use workspace::{self, Workspace}; use crate::state::ReplayableAction; -struct VimModeSetting(bool); +pub struct VimModeSetting(pub bool); #[derive(Clone, Deserialize, PartialEq)] pub struct SwitchMode(pub Mode); diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index ea01f822a7..c7ad62f155 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -25,6 +25,7 @@ theme_selector = { path = "../theme_selector" } util = { path = "../util" } picker = { path = "../picker" } workspace = { path = "../workspace" } +vim = { path = "../vim" } anyhow.workspace = true log.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 4d8df53a1b..a5d95429bd 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -10,6 +10,7 @@ use gpui::{ }; use settings::{update_settings_file, SettingsStore}; use std::{borrow::Cow, sync::Arc}; +use vim::VimModeSetting; use workspace::{ dock::DockPosition, item::Item, open_new, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, @@ -65,6 +66,7 @@ impl View for WelcomePage { let width = theme.welcome.page_width; let telemetry_settings = *settings::get::(cx); + let vim_mode_setting = settings::get::(cx).0; enum Metrics {} enum Diagnostics {} @@ -144,6 +146,27 @@ impl View for WelcomePage { ) .with_child( Flex::column() + .with_child( + theme::ui::checkbox::( + "Enable vim mode", + &theme.welcome.checkbox, + vim_mode_setting, + 0, + cx, + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file::( + fs, + cx, + move |setting| *setting = Some(checked), + ) + } + }, + ) + .contained() + .with_style(theme.welcome.checkbox_container), + ) .with_child( theme::ui::checkbox_with_label::( Flex::column() @@ -186,7 +209,7 @@ impl View for WelcomePage { "Send crash reports", &theme.welcome.checkbox, telemetry_settings.diagnostics, - 0, + 1, cx, |this, checked, cx| { if let Some(workspace) = this.workspace.upgrade(cx) { From 38d53a6fe24b49ccc198a37bc4c4e6b755a6286f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 17:09:58 +0300 Subject: [PATCH 039/141] Bump curl-sys to fix Sonoma issues with it See https://github.com/alexcrichton/curl-rust/issues/524 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c7affc76f..7c31fa6dd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2079,9 +2079,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.66+curl-8.3.0" +version = "0.4.67+curl-8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9" +checksum = "3cc35d066510b197a0f72de863736641539957628c8a42e70e27c66849e77c34" dependencies = [ "cc", "libc", From 5dbda7023517b60720d250e4109c8bb08f7c6da7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 08:59:25 -0600 Subject: [PATCH 040/141] Fix ./script/bundle to allow passing key --- crates/client/src/client.rs | 1 - crates/gpui/src/platform/mac/platform.rs | 2 -- crates/workspace/src/workspace.rs | 7 ------- crates/zed/src/main.rs | 1 - script/bundle | 10 ++++++++-- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 62cd60c55f..22e8bc06d4 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -799,7 +799,6 @@ impl Client { } } status = status_rx.next().fuse() => { - dbg!(status); return Err(anyhow!("authentication canceled")); } } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 47ad72eeb2..24ad774759 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1014,7 +1014,6 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { } extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_activity: id, _: id) { - dbg!("yay!"); let url = unsafe { let url: id = msg_send!(user_activity, webpageURL); if url == nil { @@ -1028,7 +1027,6 @@ extern "C" fn continue_user_activity(this: &mut Object, _: Sel, _: id, user_acti ) } }; - dbg!(&url); let platform = unsafe { get_foreground_platform(this) }; if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() { callback(url.into_iter().collect()); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1644f86b61..7773abc19c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,7 +4162,6 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - dbg!("join channel internal"); let should_prompt = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return false; @@ -4195,7 +4194,6 @@ async fn join_channel_internal( return Ok(false); // unreachable!() hopefully } } - dbg!("asdajdkjkasd"); let client = cx.read(|cx| active_call.read(cx).client()); @@ -4221,8 +4219,6 @@ async fn join_channel_internal( } } - dbg!("past here"); - let room = active_call .update(cx, |active_call, cx| { active_call.join_channel(channel_id, cx) @@ -4231,8 +4227,6 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; - dbg!("wow"); - let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project() { return Some(join_remote_project(project, host, app_state.clone(), cx)); @@ -4273,7 +4267,6 @@ pub fn join_channel( &mut cx, ) .await; - dbg!("joined!"); // join channel succeeded, and opened a window if matches!(result, Ok(true)) { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f25a1e14be..004e5769d5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -279,7 +279,6 @@ fn main() { } OpenRequest::JoinChannel { channel_id } => cx .update(|cx| { - dbg!("joining channel"); workspace::join_channel(channel_id, app_state.clone(), None, cx) }) .detach(), diff --git a/script/bundle b/script/bundle index 94741d290f..a1d0b305c8 100755 --- a/script/bundle +++ b/script/bundle @@ -128,7 +128,11 @@ fi echo "Copying WebRTC.framework into the frameworks folder" mkdir "${app_path}/Contents/Frameworks" -cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +if [ "$local_arch" = false ]; then + cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +else + cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" +fi if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then echo "Signing bundle with Apple-issued certificate" @@ -144,7 +148,9 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR else echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD" echo "Performing an ad-hoc signature, but this bundle should not be distributed" - codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v + echo "If you see 'The application cannot be opened for an unexpected reason,' you likely don't have the necessary entitlements to run the application in your signing keychain" + echo "You will need to download a new signing key from developer.apple.com, add it to keychain, and export MACOS_SIGNING_KEY=" + codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v fi if [[ "$target_dir" = "debug" && "$local_only" = false ]]; then From 8f4d81903c6b0322c7026c04b0df2830fbc02f10 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:28:49 -0600 Subject: [PATCH 041/141] Add "Copy Link" to channel right click menu --- crates/channel/src/channel_store.rs | 21 +++++++++++++++++++++ crates/collab_ui/src/collab_panel.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index bd72c92c7d..cd2153ad5c 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -4,6 +4,7 @@ use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat}; use anyhow::{anyhow, Result}; use client::{Client, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; +use db::RELEASE_CHANNEL; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use rpc::{ @@ -47,6 +48,26 @@ pub struct Channel { pub unseen_message_id: Option, } +impl Channel { + pub fn link(&self) -> String { + RELEASE_CHANNEL.link_prefix().to_owned() + + "channel/" + + &self.slug() + + "-" + + &self.id.to_string() + } + + pub fn slug(&self) -> String { + let slug: String = self + .name + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '-' }) + .collect(); + + slug.trim_matches(|c| c == '-').to_string() + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] pub struct ChannelPath(Arc<[ChannelId]>); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 3d66e8450a..9ec05e07c8 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -34,8 +34,8 @@ use gpui::{ }, impl_actions, platform::{CursorStyle, MouseButton, PromptLevel}, - serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, FontCache, ModelHandle, - Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + serde_json, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, FontCache, + ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -100,6 +100,11 @@ pub struct JoinChannelChat { pub channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct CopyChannelLink { + pub channel_id: u64, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct StartMoveChannelFor { channel_id: ChannelId, @@ -157,6 +162,7 @@ impl_actions!( OpenChannelNotes, JoinChannelCall, JoinChannelChat, + CopyChannelLink, LinkChannel, StartMoveChannelFor, StartLinkChannelFor, @@ -205,6 +211,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(CollabPanel::expand_selected_channel); cx.add_action(CollabPanel::open_channel_notes); cx.add_action(CollabPanel::join_channel_chat); + cx.add_action(CollabPanel::copy_channel_link); cx.add_action( |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext| { @@ -2571,6 +2578,13 @@ impl CollabPanel { }, )); + items.push(ContextMenuItem::action( + "Copy Channel Link", + CopyChannelLink { + channel_id: path.channel_id(), + }, + )); + if self.channel_store.read(cx).is_user_admin(path.channel_id()) { let parent_id = path.parent_id(); @@ -3219,6 +3233,15 @@ impl CollabPanel { }); } } + + fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext) { + let channel_store = self.channel_store.read(cx); + let Some(channel) = channel_store.channel_for_id(action.channel_id) else { + return; + }; + let item = ClipboardItem::new(channel.link()); + cx.write_to_clipboard(item) + } } fn render_tree_branch( From 6084486dcdb98685cdc589cff385c64a5a63cb7e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 09:34:18 -0600 Subject: [PATCH 042/141] Code quality --- crates/client/src/client.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 27 ++++++++++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 22e8bc06d4..5767ac54b7 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -798,7 +798,7 @@ impl Client { } } } - status = status_rx.next().fuse() => { + _ = status_rx.next().fuse() => { return Err(anyhow!("authentication canceled")); } } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9ec05e07c8..8a8fde88ee 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1976,21 +1976,18 @@ impl CollabPanel { let style = collab_theme.channel_name.inactive_state(); Flex::row() .with_child( - Label::new( - channel.name.clone().to_owned() + channel_id.to_string().as_str(), - style.text.clone(), - ) - .contained() - .with_style(style.container) - .aligned() - .left() - .with_tooltip::( - ix, - "Join channel", - None, - theme.tooltip.clone(), - cx, - ), + Label::new(channel.name.clone(), style.text.clone()) + .contained() + .with_style(style.container) + .aligned() + .left() + .with_tooltip::( + ix, + "Join channel", + None, + theme.tooltip.clone(), + cx, + ), ) .with_children({ let participants = From abfb4490d590f33ffefce2d945b540b60cad4d99 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:05:26 -0600 Subject: [PATCH 043/141] Focus the currently active project if there is one (also consider your own projects in "most_active_projects") --- crates/call/src/room.rs | 9 ++++++++- crates/workspace/src/workspace.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 5419e00f02..b7aeee90e2 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -605,7 +605,7 @@ impl Room { } /// Returns the most 'active' projects, defined as most people in the project - pub fn most_active_project(&self) -> Option<(u64, u64)> { + pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> { let mut projects = HashMap::default(); let mut hosts = HashMap::default(); @@ -622,6 +622,13 @@ impl Room { } } + if let Some(user) = self.user_store.read(cx).current_user() { + for project in &self.local_participant.projects { + *projects.entry(project.id).or_insert(0) += 1; + hosts.insert(project.id, user.id); + } + } + let mut pairs: Vec<(u64, usize)> = projects.into_iter().collect(); pairs.sort_by_key(|(_, count)| *count as i32); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7773abc19c..e690757af7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4162,16 +4162,37 @@ async fn join_channel_internal( active_call: &ModelHandle, cx: &mut AsyncAppContext, ) -> Result { - let should_prompt = active_call.read_with(cx, |active_call, cx| { + let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { - return false; + return (false, None); }; - room.is_sharing_project() + let already_in_channel = room.channel_id() == Some(channel_id); + let should_prompt = room.is_sharing_project() && room.remote_participants().len() > 0 - && room.channel_id() != Some(channel_id) + && !already_in_channel; + let open_room = if already_in_channel { + active_call.room().cloned() + } else { + None + }; + (should_prompt, open_room) }); + if let Some(room) = open_room { + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project(cx) { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } + + None + }); + if let Some(task) = task { + task.await?; + } + return anyhow::Ok(true); + } + if should_prompt { if let Some(workspace) = requesting_window { if let Some(window) = workspace.update(cx, |cx| cx.window()) { @@ -4228,7 +4249,7 @@ async fn join_channel_internal( room.update(cx, |room, _| room.next_room_update()).await; let task = room.update(cx, |room, cx| { - if let Some((project, host)) = room.most_active_project() { + if let Some((project, host)) = room.most_active_project(cx) { return Some(join_remote_project(project, host, app_state.clone(), cx)); } From 7020050b069474ab9145cef6578a95d68f217f43 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 9 Oct 2023 14:28:53 -0400 Subject: [PATCH 044/141] Fix `hover_popover.rs` after bad rebase --- crates/editor/src/editor.rs | 35 ++------ crates/editor/src/hover_popover.rs | 128 +++++++++++++---------------- crates/language/src/markdown.rs | 31 ++++++- 3 files changed, 92 insertions(+), 102 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6585611040..b8c9690b90 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,7 +60,6 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - markdown::MarkdownHighlight, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, @@ -139,45 +138,21 @@ pub fn render_parsed_markdown( .highlights .iter() .filter_map(|(range, highlight)| { - let highlight = match highlight { - MarkdownHighlight::Style(style) => { - let mut highlight = HighlightStyle::default(); - - if style.italic { - highlight.italic = Some(true); - } - - if style.underline { - highlight.underline = Some(fonts::Underline { - thickness: 1.0.into(), - ..Default::default() - }); - } - - if style.weight != fonts::Weight::default() { - highlight.weight = Some(style.weight); - } - - highlight - } - - MarkdownHighlight::Code(id) => id.style(&editor_style.syntax)?, - }; - + let highlight = highlight.to_highlight_style(&editor_style.syntax)?; Some((range.clone(), highlight)) }) .collect::>(), ) - .with_custom_runs(parsed.region_ranges, move |ix, bounds, scene, _| { + .with_custom_runs(parsed.region_ranges, move |ix, bounds, cx| { region_id += 1; let region = parsed.regions[ix].clone(); if let Some(url) = region.link_url { - scene.push_cursor_region(CursorRegion { + cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); - scene.push_mouse_region( + cx.scene().push_mouse_region( MouseRegion::new::(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) @@ -186,7 +161,7 @@ pub fn render_parsed_markdown( } if region.code { - scene.push_quad(gpui::Quad { + cx.scene().push_quad(gpui::Quad { bounds, background: Some(code_span_background_color), border: Default::default(), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 7917d57865..d5ccb481b2 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, - link_go_to_definition::{DocumentRange, InlayRange}, + link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, ExcerptId, RangeToAnchorExt, }; @@ -51,19 +51,18 @@ pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewC pub struct InlayHover { pub excerpt: ExcerptId, - pub triggered_from: InlayOffset, - pub range: InlayRange, + pub range: InlayHighlight, pub tooltip: HoverBlock, } pub fn find_hovered_hint_part( label_parts: Vec, - hint_range: Range, + hint_start: InlayOffset, hovered_offset: InlayOffset, ) -> Option<(InlayHintLabelPart, Range)> { - if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end { - let mut hovered_character = (hovered_offset - hint_range.start).0; - let mut part_start = hint_range.start; + if hovered_offset >= hint_start { + let mut hovered_character = (hovered_offset - hint_start).0; + let mut part_start = hint_start; for part in label_parts { let part_len = part.value.chars().count(); if hovered_character > part_len { @@ -89,10 +88,8 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie }; if let Some(InfoPopover { symbol_range, .. }) = &editor.hover_state.info_popover { - if let DocumentRange::Inlay(range) = symbol_range { - if (range.highlight_start..range.highlight_end) - .contains(&inlay_hover.triggered_from) - { + if let RangeInEditor::Inlay(range) = symbol_range { + if range == &inlay_hover.range { // Hover triggered from same location as last time. Don't show again. return; } @@ -100,18 +97,6 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie hide_hover(editor, cx); } - let snapshot = editor.snapshot(cx); - // Don't request again if the location is the same as the previous request - if let Some(triggered_from) = editor.hover_state.triggered_from { - if inlay_hover.triggered_from - == snapshot - .display_snapshot - .anchor_to_inlay_offset(triggered_from) - { - return; - } - } - let task = cx.spawn(|this, mut cx| { async move { cx.background() @@ -127,7 +112,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie let hover_popover = InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Inlay(inlay_hover.range), + symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), blocks, parsed_content, }; @@ -331,7 +316,7 @@ fn show_hover( Some(InfoPopover { project: project.clone(), - symbol_range: DocumentRange::Text(range), + symbol_range: RangeInEditor::Text(range), blocks, parsed_content, }) @@ -449,8 +434,8 @@ impl HoverState { self.info_popover .as_ref() .map(|info_popover| match &info_popover.symbol_range { - DocumentRange::Text(range) => &range.start, - DocumentRange::Inlay(range) => &range.inlay_position, + RangeInEditor::Text(range) => &range.start, + RangeInEditor::Inlay(range) => &range.inlay_position, }) })?; let point = anchor.to_display_point(&snapshot.display_snapshot); @@ -476,7 +461,7 @@ impl HoverState { #[derive(Debug, Clone)] pub struct InfoPopover { pub project: ModelHandle, - symbol_range: DocumentRange, + symbol_range: RangeInEditor, pub blocks: Vec, parsed_content: ParsedMarkdown, } @@ -587,14 +572,12 @@ mod tests { inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, link_go_to_definition::update_inlay_link_and_hover_points, test::editor_lsp_test_context::EditorLspTestContext, + InlayId, }; use collections::BTreeSet; - use gpui::fonts::Weight; + use gpui::fonts::{HighlightStyle, Underline, Weight}; use indoc::indoc; - use language::{ - language_settings::InlayHintSettings, markdown::MarkdownHighlightStyle, Diagnostic, - DiagnosticSet, - }; + use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet}; use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; @@ -896,17 +879,16 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { - use markdown::MarkdownHighlight; - init_test(cx, |_| {}); cx.add_window(|cx| { let editor = Editor::single_line(None, cx); + let style = editor.style(cx); struct Row { blocks: Vec, expected_marked_text: String, - expected_styles: Vec, + expected_styles: Vec, } let rows = &[ @@ -917,10 +899,10 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - weight: Weight::BOLD, + expected_styles: vec![HighlightStyle { + weight: Some(Weight::BOLD), ..Default::default() - })], + }], }, // Links Row { @@ -929,10 +911,13 @@ mod tests { kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Lists Row { @@ -957,10 +942,13 @@ mod tests { - «c» - d" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, // Multi-paragraph list items Row { @@ -988,10 +976,13 @@ mod tests { - ten - six" .unindent(), - expected_styles: vec![MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, + expected_styles: vec![HighlightStyle { + underline: Some(Underline { + thickness: 1.0.into(), + ..Default::default() + }), ..Default::default() - })], + }], }, ]; @@ -1012,8 +1003,18 @@ mod tests { rendered.text, expected_text, "wrong text for input {blocks:?}" ); + + let rendered_highlights: Vec<_> = rendered + .highlights + .iter() + .filter_map(|(range, highlight)| { + let highlight = highlight.to_highlight_style(&style.syntax)?; + Some((range.clone(), highlight)) + }) + .collect(); + assert_eq!( - rendered.highlights, expected_highlights, + rendered_highlights, expected_highlights, "wrong highlights for input {blocks:?}" ); } @@ -1247,25 +1248,16 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - - let expected_new_type_label_start = InlayOffset(entire_inlay_start.0 + ": ".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_new_type_label_start, - highlight_end: InlayOffset( - expected_new_type_label_start.0 + new_type_label.len() - ), + range: ": ".len()..": ".len() + new_type_label.len(), }), "Popover range should match the new type label part" ); @@ -1309,23 +1301,17 @@ mod tests { .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); cx.foreground().run_until_parked(); cx.update_editor(|editor, cx| { - let snapshot = editor.snapshot(cx); let hover_state = &editor.hover_state; assert!(hover_state.diagnostic_popover.is_none() && hover_state.info_popover.is_some()); let popover = hover_state.info_popover.as_ref().unwrap(); let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx)); - let entire_inlay_start = snapshot.display_point_to_inlay_offset( - inlay_range.start.to_display_point(&snapshot), - Bias::Left, - ); - let expected_struct_label_start = - InlayOffset(entire_inlay_start.0 + ": ".len() + new_type_label.len() + "<".len()); assert_eq!( popover.symbol_range, - DocumentRange::Inlay(InlayRange { + RangeInEditor::Inlay(InlayHighlight { + inlay: InlayId::Hint(0), inlay_position: buffer_snapshot.anchor_at(inlay_range.start, Bias::Right), - highlight_start: expected_struct_label_start, - highlight_end: InlayOffset(expected_struct_label_start.0 + struct_label.len()), + range: ": ".len() + new_type_label.len() + "<".len() + ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(), }), "Popover range should match the struct label part" ); diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 9f29e7cb88..8be15e81f6 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -2,7 +2,7 @@ use std::ops::Range; use std::sync::Arc; use crate::{HighlightId, Language, LanguageRegistry}; -use gpui::fonts::Weight; +use gpui::fonts::{self, HighlightStyle, Weight}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; #[derive(Debug, Clone)] @@ -19,6 +19,35 @@ pub enum MarkdownHighlight { Code(HighlightId), } +impl MarkdownHighlight { + pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { + match self { + MarkdownHighlight::Style(style) => { + let mut highlight = HighlightStyle::default(); + + if style.italic { + highlight.italic = Some(true); + } + + if style.underline { + highlight.underline = Some(fonts::Underline { + thickness: 1.0.into(), + ..Default::default() + }); + } + + if style.weight != fonts::Weight::default() { + highlight.weight = Some(style.weight); + } + + Some(highlight) + } + + MarkdownHighlight::Code(id) => id.style(theme), + } + } +} + #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct MarkdownHighlightStyle { pub italic: bool, From 162cb19cff1efaaa127dd7619a5351facc545c1c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 12:59:18 -0600 Subject: [PATCH 045/141] Only allow one release channel in a call --- .../20221109000000_test_schema.sql | 1 + ...009181554_add_release_channel_to_rooms.sql | 1 + crates/collab/src/db/queries/rooms.rs | 32 +++++-- crates/collab/src/db/tables/room.rs | 1 + crates/collab/src/db/tests.rs | 2 + crates/collab/src/db/tests/channel_tests.rs | 20 +++- crates/collab/src/db/tests/db_tests.rs | 92 ++++++++++++++++++- crates/collab/src/rpc.rs | 22 ++++- 8 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 2d963ff15f..a9b8d9709d 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,6 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, + "release_channel" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql new file mode 100644 index 0000000000..95d3c400fc --- /dev/null +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -0,0 +1 @@ +ALTER TABLE rooms ADD COLUMN release_channel TEXT; diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index b103ae1c73..6589f23791 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -107,10 +107,12 @@ impl Database { user_id: UserId, connection: ConnectionId, live_kit_room: &str, + release_channel: &str, ) -> Result { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), + release_channel: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -270,20 +272,31 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, + collab_release_channel: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelId { + enum QueryChannelIdAndReleaseChannel { ChannelId, + ReleaseChannel, + } + + let (channel_id, release_channel): (Option, Option) = + room::Entity::find() + .select_only() + .column(room::Column::ChannelId) + .column(room::Column::ReleaseChannel) + .filter(room::Column::Id.eq(room_id)) + .into_values::<_, QueryChannelIdAndReleaseChannel>() + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if let Some(release_channel) = release_channel { + if &release_channel != collab_release_channel { + Err(anyhow!("must join using the {} release", release_channel))?; + } } - let channel_id: Option = room::Entity::find() - .select_only() - .column(room::Column::ChannelId) - .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelId>() - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("no such room"))?; #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryParticipantIndices { @@ -300,6 +313,7 @@ impl Database { .into_values::<_, QueryParticipantIndices>() .all(&*tx) .await?; + let mut participant_index = 0; while existing_participant_indices.contains(&participant_index) { participant_index += 1; diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index f72f7000a7..7f31edcdbd 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,6 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, + pub release_channel: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 75584ff90b..6a91fd6ffe 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -12,6 +12,8 @@ use sea_orm::ConnectionTrait; use sqlx::migrate::MigrateDatabase; use std::sync::Arc; +const TEST_RELEASE_CHANNEL: &'static str = "test"; + pub struct TestDb { pub db: Option>, pub connection: Option, diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 429852d128..2631e0d191 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -5,7 +5,11 @@ use rpc::{ }; use crate::{ - db::{queries::channels::ChannelGraph, tests::graph, ChannelId, Database, NewUserParams}, + db::{ + queries::channels::ChannelGraph, + tests::{graph, TEST_RELEASE_CHANNEL}, + ChannelId, Database, NewUserParams, + }, test_both_dbs, }; use std::sync::Arc; @@ -206,7 +210,12 @@ async fn test_joining_channels(db: &Arc) { // can join a room with membership to its channel let joined_room = db - .join_room(room_1, user_1, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_1, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL, + ) .await .unwrap(); assert_eq!(joined_room.room.participants.len(), 1); @@ -214,7 +223,12 @@ async fn test_joining_channels(db: &Arc) { drop(joined_room); // cannot join a room without membership to its channel assert!(db - .join_room(room_1, user_2, ConnectionId { owner_id, id: 1 }) + .join_room( + room_1, + user_2, + ConnectionId { owner_id, id: 1 }, + TEST_RELEASE_CHANNEL + ) .await .is_err()); } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 9a617166fe..1520e081c0 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -479,7 +479,7 @@ async fn test_project_count(db: &Arc) { .unwrap(); let room_id = RoomId::from_proto( - db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "") + db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "", "dev") .await .unwrap() .id, @@ -493,9 +493,14 @@ async fn test_project_count(db: &Arc) { ) .await .unwrap(); - db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 }) - .await - .unwrap(); + db.join_room( + room_id, + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "dev", + ) + .await + .unwrap(); assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0); db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[]) @@ -575,6 +580,85 @@ async fn test_fuzzy_search_users() { } } +test_both_dbs!( + test_non_matching_release_channels, + test_non_matching_release_channels_postgres, + test_non_matching_release_channels_sqlite +); + +async fn test_non_matching_release_channels(db: &Arc) { + let owner_id = db.create_server("test").await.unwrap().0 as u32; + + let user1 = db + .create_user( + &format!("admin@example.com"), + true, + NewUserParams { + github_login: "admin".into(), + github_user_id: 0, + invite_count: 0, + }, + ) + .await + .unwrap(); + let user2 = db + .create_user( + &format!("user@example.com"), + false, + NewUserParams { + github_login: "user".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap(); + + let room = db + .create_room( + user1.user_id, + ConnectionId { owner_id, id: 0 }, + "", + "stable", + ) + .await + .unwrap(); + + db.call( + RoomId::from_proto(room.id), + user1.user_id, + ConnectionId { owner_id, id: 0 }, + user2.user_id, + None, + ) + .await + .unwrap(); + + // User attempts to join from preview + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "preview", + ) + .await; + + assert!(result.is_err()); + + // User switches to stable + let result = db + .join_room( + RoomId::from_proto(room.id), + user2.user_id, + ConnectionId { owner_id, id: 1 }, + "stable", + ) + .await; + + assert!(result.is_ok()) +} + fn build_background_executor() -> Arc { Deterministic::new(0).build_background() } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 6171803341..70052468d4 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -63,6 +63,7 @@ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; use tracing::{info_span, instrument, Instrument}; +use util::channel::RELEASE_CHANNEL_NAME; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -957,7 +958,12 @@ async fn create_room( let room = session .db() .await - .create_room(session.user_id, session.connection_id, &live_kit_room) + .create_room( + session.user_id, + session.connection_id, + &live_kit_room, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; response.send(proto::CreateRoomResponse { @@ -979,7 +985,12 @@ async fn join_room( let room = session .db() .await - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; room_updated(&room.room, &session.peer); room.into_inner() @@ -2616,7 +2627,12 @@ async fn join_channel( let room_id = db.room_id_for_channel(channel_id).await?; let joined_room = db - .join_room(room_id, session.user_id, session.connection_id) + .join_room( + room_id, + session.user_id, + session.connection_id, + RELEASE_CHANNEL_NAME.as_str(), + ) .await?; let live_kit_connection_info = session.live_kit_client.as_ref().and_then(|live_kit| { From fb57299a1d61276999607f1da5a7670b0f793e76 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 13:40:22 -0600 Subject: [PATCH 046/141] re-trigger build with new profile? From a0ab9fe56b4ceb952631e75e0917fc3cc64266c1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 12:40:36 -0700 Subject: [PATCH 047/141] Unify the 2 local zed scripts, take a flag for instance count --- script/start-local-collaboration | 59 --------------------- script/zed-local | 88 ++++++++++++++++++++++++++++++++ script/zed-with-local-servers | 6 --- 3 files changed, 88 insertions(+), 65 deletions(-) delete mode 100755 script/start-local-collaboration create mode 100755 script/zed-local delete mode 100755 script/zed-with-local-servers diff --git a/script/start-local-collaboration b/script/start-local-collaboration deleted file mode 100755 index 0c4e60f9c3..0000000000 --- a/script/start-local-collaboration +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -set -e - -if [[ -z "$GITHUB_TOKEN" ]]; then - cat <<-MESSAGE -Missing \`GITHUB_TOKEN\` environment variable. This token is needed -for fetching your GitHub identity from the command-line. - -Create an access token here: https://github.com/settings/tokens -Then edit your \`~/.zshrc\` (or other shell initialization script), -adding a line like this: - - export GITHUB_TOKEN="(the token)" - -MESSAGE - exit 1 -fi - -# Install jq if it's not installed -if ! command -v jq &> /dev/null; then - echo "Installing jq..." - brew install jq -fi - -# Start one Zed instance as the current user and a second instance with a different user. -username_1=$(curl -sH "Authorization: bearer $GITHUB_TOKEN" https://api.github.com/user | jq -r .login) -username_2=nathansobo -if [[ $username_1 == $username_2 ]]; then - username_2=as-cii -fi - -# Make each Zed instance take up half of the screen. -output=$(system_profiler SPDisplaysDataType -json) -main_display=$(echo "$output" | jq '.SPDisplaysDataType[].spdisplays_ndrvs[] | select(.spdisplays_main == "spdisplays_yes")') -resolution=$(echo "$main_display" | jq -r '._spdisplays_resolution') -width=$(echo "$resolution" | jq -Rr 'match("(\\d+) x (\\d+)").captures[0].string') -half_width=$(($width / 2)) -height=$(echo "$resolution" | jq -Rr 'match("(\\d+) x (\\d+)").captures[1].string') -y=0 - -position_1=0,${y} -position_2=${half_width},${y} - -# Authenticate using the collab server's admin secret. -export ZED_STATELESS=1 -export ZED_ALWAYS_ACTIVE=1 -export ZED_ADMIN_API_TOKEN=secret -export ZED_SERVER_URL=http://localhost:8080 -export ZED_WINDOW_SIZE=${half_width},${height} - -cargo build -sleep 0.5 - -# Start the two Zed child processes. Open the given paths with the first instance. -trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT -ZED_IMPERSONATE=${ZED_IMPERSONATE:=${username_1}} ZED_WINDOW_POSITION=${position_1} target/debug/Zed $@ & -SECOND=true ZED_IMPERSONATE=${username_2} ZED_WINDOW_POSITION=${position_2} target/debug/Zed & -wait diff --git a/script/zed-local b/script/zed-local new file mode 100755 index 0000000000..683e31ef14 --- /dev/null +++ b/script/zed-local @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +const {spawn, execFileSync} = require('child_process') + +const RESOLUTION_REGEX = /(\d+) x (\d+)/ +const DIGIT_FLAG_REGEX = /^--?(\d+)$/ + +const args = process.argv.slice(2) + +// Parse the number of Zed instances to spawn. +let instanceCount = 1 +const digitMatch = args[0]?.match(DIGIT_FLAG_REGEX) +if (digitMatch) { + instanceCount = parseInt(digitMatch[1]) + args.shift() +} +if (instanceCount > 4) { + throw new Error('Cannot spawn more than 4 instances') +} + +// Parse the resolution of the main screen +const displayInfo = JSON.parse( + execFileSync( + 'system_profiler', + ['SPDisplaysDataType', '-json'], + {encoding: 'utf8'} + ) +) +const mainDisplayResolution = displayInfo + ?.SPDisplaysDataType[0] + ?.spdisplays_ndrvs + ?.find(entry => entry.spdisplays_main === "spdisplays_yes") + ?._spdisplays_resolution + ?.match(RESOLUTION_REGEX) +if (!mainDisplayResolution) { + throw new Error('Could not parse screen resolution') +} +const screenWidth = parseInt(mainDisplayResolution[1]) +const screenHeight = parseInt(mainDisplayResolution[2]) + +// Determine the window size for each instance +let instanceWidth = screenWidth +let instanceHeight = screenHeight +if (instanceCount > 1) { + instanceWidth = Math.floor(screenWidth / 2) + if (instanceCount > 2) { + instanceHeight = Math.floor(screenHeight / 2) + } +} + +let users = [ + 'nathansobo', + 'as-cii', + 'maxbrunsfeld', + 'iamnbutler' +] + +// If a user is specified, make sure it's first in the list +const user = process.env.ZED_IMPERSONATE +if (user) { + users = [user].concat(users.filter(u => u !== user)) +} + +const positions = [ + '0,0', + `${instanceWidth},0`, + `0,${instanceHeight}`, + `${instanceWidth},${instanceHeight}` +] + +execFileSync('cargo', ['build'], {stdio: 'inherit'}) + +setTimeout(() => { + for (let i = 0; i < instanceCount; i++) { + spawn('target/debug/Zed', i == 0 ? args : [], { + stdio: 'inherit', + env: { + ZED_IMPERSONATE: users[i], + ZED_WINDOW_POSITION: positions[i], + ZED_STATELESS: '1', + ZED_ALWAYS_ACTIVE: '1', + ZED_SERVER_URL: 'http://localhost:8080', + ZED_ADMIN_API_TOKEN: 'secret', + ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}` + } + }) + } +}, 0.1) diff --git a/script/zed-with-local-servers b/script/zed-with-local-servers deleted file mode 100755 index e1b224de60..0000000000 --- a/script/zed-with-local-servers +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -: "${ZED_IMPERSONATE:=as-cii}" -export ZED_IMPERSONATE - -ZED_ADMIN_API_TOKEN=secret ZED_SERVER_URL=http://localhost:8080 cargo run $@ From 1e4f5145cf156344e35f451ebb44722fde9111fc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 12:48:35 -0700 Subject: [PATCH 048/141] Update docs to refer to new zed-local script --- README.md | 4 +--- docs/building-zed.md | 3 +-- docs/local-collaboration.md | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b3d4987526..eed8dd4d91 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,7 @@ foreman start If you want to run Zed pointed at the local servers, you can run: ``` -script/zed-with-local-servers -# or... -script/zed-with-local-servers --release +script/zed-local ``` ### Dump element JSON diff --git a/docs/building-zed.md b/docs/building-zed.md index 6981913285..ec4538cf85 100644 --- a/docs/building-zed.md +++ b/docs/building-zed.md @@ -75,8 +75,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil - If you are just using the latest version, but not working on zed: - `cargo run --release` - If you need to run the collaboration server locally: - - `script/zed-with-local-servers` - - If you need to test collaboration with mutl + - `script/zed-local` ## Troubleshooting diff --git a/docs/local-collaboration.md b/docs/local-collaboration.md index 7d8054af67..4c059c0878 100644 --- a/docs/local-collaboration.md +++ b/docs/local-collaboration.md @@ -17,6 +17,6 @@ ## Testing collab locally 1. Run `foreman start` from the root of the repo. -1. In another terminal run `script/start-local-collaboration`. +1. In another terminal run `script/zed-local -2`. 1. Two copies of Zed will open. Add yourself as a contact in the one that is not you. 1. Start a collaboration session as normal with any open project. From 9cba45910eab6e8009af6ad51b6ac13c0df61a0f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 18:54:57 +0300 Subject: [PATCH 049/141] Ignore history items' paths when matching search queries --- crates/file_finder/src/file_finder.rs | 127 +++++++++++++++++++++++++- crates/fuzzy/src/matcher.rs | 2 +- crates/fuzzy/src/paths.rs | 6 +- 3 files changed, 126 insertions(+), 9 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 6e587d8c98..222a9c650a 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -107,13 +107,23 @@ fn matching_history_item_paths( ) -> HashMap, PathMatch> { let history_items_by_worktrees = history_items .iter() - .map(|found_path| { - let path = &found_path.project.path; + .filter_map(|found_path| { let candidate = PathMatchCandidate { - path, - char_bag: CharBag::from_iter(path.to_string_lossy().to_lowercase().chars()), + path: &found_path.project.path, + // Only match history items names, otherwise their paths may match too many queries, producing false positives. + // E.g. `foo` would match both `something/foo/bar.rs` and `something/foo/foo.rs` and if the former is a history item, + // it would be shown first always, despite the latter being a better match. + char_bag: CharBag::from_iter( + found_path + .project + .path + .file_name()? + .to_string_lossy() + .to_lowercase() + .chars(), + ), }; - (found_path.project.worktree_id, candidate) + Some((found_path.project.worktree_id, candidate)) }) .fold( HashMap::default(), @@ -1803,6 +1813,113 @@ mod tests { }); } + #[gpui::test] + async fn test_history_items_vs_very_good_external_match( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "collab_ui": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + "collab_ui.rs": "// Fourth Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + // generate some history to select from + open_close_queried_buffer( + "fir", + 1, + "first.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "sec", + 1, + "second.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "thi", + 1, + "third.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "sec", + 1, + "second.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + + cx.dispatch_action(window.into(), Toggle); + let query = "collab_ui"; + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches(query.to_string(), cx) + }) + .await; + finder.read_with(cx, |finder, _| { + let delegate = finder.delegate(); + assert!( + delegate.matches.history.is_empty(), + "History items should not math query {query}, they should be matched by name only" + ); + + let search_entries = delegate + .matches + .search + .iter() + .map(|e| e.path.to_path_buf()) + .collect::>(); + assert_eq!( + search_entries.len(), + 4, + "All history and the new file should be found after query {query} as search results" + ); + assert_eq!( + search_entries, + vec![ + PathBuf::from("collab_ui/collab_ui.rs"), + PathBuf::from("collab_ui/third.rs"), + PathBuf::from("collab_ui/first.rs"), + PathBuf::from("collab_ui/second.rs"), + ], + "Despite all search results having the same directory name, the most matching one should be on top" + ); + }); + } + async fn open_close_queried_buffer( input: &str, expected_matches: usize, diff --git a/crates/fuzzy/src/matcher.rs b/crates/fuzzy/src/matcher.rs index dafafe40a0..e808a4886f 100644 --- a/crates/fuzzy/src/matcher.rs +++ b/crates/fuzzy/src/matcher.rs @@ -441,7 +441,7 @@ mod tests { score, worktree_id: 0, positions: Vec::new(), - path: candidate.path.clone(), + path: Arc::from(candidate.path), path_prefix: "".into(), distance_to_relative_ancestor: usize::MAX, }, diff --git a/crates/fuzzy/src/paths.rs b/crates/fuzzy/src/paths.rs index 4eb31936a8..d8fae471e1 100644 --- a/crates/fuzzy/src/paths.rs +++ b/crates/fuzzy/src/paths.rs @@ -14,7 +14,7 @@ use crate::{ #[derive(Clone, Debug)] pub struct PathMatchCandidate<'a> { - pub path: &'a Arc, + pub path: &'a Path, pub char_bag: CharBag, } @@ -120,7 +120,7 @@ pub fn match_fixed_path_set( score, worktree_id, positions: Vec::new(), - path: candidate.path.clone(), + path: Arc::from(candidate.path), path_prefix: Arc::from(""), distance_to_relative_ancestor: usize::MAX, }, @@ -195,7 +195,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( score, worktree_id, positions: Vec::new(), - path: candidate.path.clone(), + path: Arc::from(candidate.path), path_prefix: candidate_set.prefix(), distance_to_relative_ancestor: relative_to.as_ref().map_or( usize::MAX, From af90077a6aaf12a2af5871a506ce544bbf0e8046 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 13:30:14 -0700 Subject: [PATCH 050/141] Add failing test for switching leaders in a pane --- crates/collab/src/tests/following_tests.rs | 156 +++++++++------------ 1 file changed, 70 insertions(+), 86 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 3a489b9ac3..f3857e3db3 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -184,20 +184,12 @@ async fn test_basic_following( // All clients see that clients B and C are following client A. cx_c.foreground().run_until_parked(); - for (name, active_call, cx) in [ - ("A", &active_call_a, &cx_a), - ("B", &active_call_b, &cx_b), - ("C", &active_call_c, &cx_c), - ("D", &active_call_d, &cx_d), - ] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_a, project_id), - &[peer_id_b, peer_id_c], - "checking followers for A as {name}" - ); - }); + for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] { + assert_eq!( + followers_by_leader(project_id, cx), + &[(peer_id_a, vec![peer_id_b, peer_id_c])], + "followers seen by {name}" + ); } // Client C unfollows client A. @@ -207,46 +199,39 @@ async fn test_basic_following( // All clients see that clients B is following client A. cx_c.foreground().run_until_parked(); - for (name, active_call, cx) in [ - ("A", &active_call_a, &cx_a), - ("B", &active_call_b, &cx_b), - ("C", &active_call_c, &cx_c), - ("D", &active_call_d, &cx_d), - ] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_a, project_id), - &[peer_id_b], - "checking followers for A as {name}" - ); - }); + for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] { + assert_eq!( + followers_by_leader(project_id, cx), + &[(peer_id_a, vec![peer_id_b])], + "followers seen by {name}" + ); } // Client C re-follows client A. - workspace_c.update(cx_c, |workspace, cx| { - workspace.follow(peer_id_a, cx); - }); + workspace_c + .update(cx_c, |workspace, cx| { + workspace.follow(peer_id_a, cx).unwrap() + }) + .await + .unwrap(); // All clients see that clients B and C are following client A. cx_c.foreground().run_until_parked(); - for (name, active_call, cx) in [ - ("A", &active_call_a, &cx_a), - ("B", &active_call_b, &cx_b), - ("C", &active_call_c, &cx_c), - ("D", &active_call_d, &cx_d), - ] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_a, project_id), - &[peer_id_b, peer_id_c], - "checking followers for A as {name}" - ); - }); + for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] { + assert_eq!( + followers_by_leader(project_id, cx), + &[(peer_id_a, vec![peer_id_b, peer_id_c])], + "followers seen by {name}" + ); } - // Client D follows client C. + // Client D follows client B, then switches to following client C. + workspace_d + .update(cx_d, |workspace, cx| { + workspace.follow(peer_id_b, cx).unwrap() + }) + .await + .unwrap(); workspace_d .update(cx_d, |workspace, cx| { workspace.follow(peer_id_c, cx).unwrap() @@ -256,20 +241,15 @@ async fn test_basic_following( // All clients see that D is following C cx_d.foreground().run_until_parked(); - for (name, active_call, cx) in [ - ("A", &active_call_a, &cx_a), - ("B", &active_call_b, &cx_b), - ("C", &active_call_c, &cx_c), - ("D", &active_call_d, &cx_d), - ] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_c, project_id), - &[peer_id_d], - "checking followers for C as {name}" - ); - }); + for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] { + assert_eq!( + followers_by_leader(project_id, cx), + &[ + (peer_id_a, vec![peer_id_b, peer_id_c]), + (peer_id_c, vec![peer_id_d]) + ], + "followers seen by {name}" + ); } // Client C closes the project. @@ -278,32 +258,12 @@ async fn test_basic_following( // Clients A and B see that client B is following A, and client C is not present in the followers. cx_c.foreground().run_until_parked(); - for (name, active_call, cx) in [("A", &active_call_a, &cx_a), ("B", &active_call_b, &cx_b)] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_a, project_id), - &[peer_id_b], - "checking followers for A as {name}" - ); - }); - } - - // All clients see that no-one is following C - for (name, active_call, cx) in [ - ("A", &active_call_a, &cx_a), - ("B", &active_call_b, &cx_b), - ("C", &active_call_c, &cx_c), - ("D", &active_call_d, &cx_d), - ] { - active_call.read_with(*cx, |call, cx| { - let room = call.room().unwrap().read(cx); - assert_eq!( - room.followers_for(peer_id_c, project_id), - &[], - "checking followers for C as {name}" - ); - }); + for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] { + assert_eq!( + followers_by_leader(project_id, cx), + &[(peer_id_a, vec![peer_id_b]),], + "followers seen by {name}" + ); } // When client A activates a different editor, client B does so as well. @@ -1667,6 +1627,30 @@ struct PaneSummary { items: Vec<(bool, String)>, } +fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec)> { + cx.read(|cx| { + let active_call = ActiveCall::global(cx).read(cx); + let peer_id = active_call.client().peer_id(); + let room = active_call.room().unwrap().read(cx); + let mut result = room + .remote_participants() + .values() + .map(|participant| participant.peer_id) + .chain(peer_id) + .filter_map(|peer_id| { + let followers = room.followers_for(peer_id, project_id); + if followers.is_empty() { + None + } else { + Some((peer_id, followers.to_vec())) + } + }) + .collect::>(); + result.sort_by_key(|e| e.0); + result + }) +} + fn pane_summaries(workspace: &ViewHandle, cx: &mut TestAppContext) -> Vec { workspace.read_with(cx, |workspace, cx| { let active_pane = workspace.active_pane(); From ca735ad70f5229693466e9fa3511a9e3447c47b2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 13:32:38 -0700 Subject: [PATCH 051/141] Ensure there's only one leader per pane --- crates/workspace/src/pane_group.rs | 33 +++---- crates/workspace/src/workspace.rs | 137 ++++++++++++++--------------- 2 files changed, 77 insertions(+), 93 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index c12cb261c8..aef03dcda0 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,10 +1,7 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; - -use crate::{ - pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace, -}; +use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; +use collections::HashMap; use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, @@ -13,6 +10,7 @@ use gpui::{ }; use project::Project; use serde::Deserialize; +use std::{cell::RefCell, rc::Rc, sync::Arc}; use theme::Theme; const HANDLE_HITBOX_SIZE: f32 = 4.0; @@ -95,7 +93,7 @@ impl PaneGroup { &self, project: &ModelHandle, theme: &Theme, - follower_states: &FollowerStatesByLeader, + follower_states: &HashMap, FollowerState>, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, zoomed: Option<&AnyViewHandle>, @@ -162,7 +160,7 @@ impl Member { project: &ModelHandle, basis: usize, theme: &Theme, - follower_states: &FollowerStatesByLeader, + follower_states: &HashMap, FollowerState>, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, zoomed: Option<&AnyViewHandle>, @@ -179,19 +177,10 @@ impl Member { ChildView::new(pane, cx).into_any() }; - let leader = follower_states - .iter() - .find_map(|(leader_id, follower_states)| { - if follower_states.contains_key(pane) { - Some(leader_id) - } else { - None - } - }) - .and_then(|leader_id| { - let room = active_call?.read(cx).room()?.read(cx); - room.remote_participant_for_peer_id(*leader_id) - }); + let leader = follower_states.get(pane).and_then(|state| { + let room = active_call?.read(cx).room()?.read(cx); + room.remote_participant_for_peer_id(state.leader_id) + }); let mut leader_border = Border::default(); let mut leader_status_box = None; @@ -486,7 +475,7 @@ impl PaneAxis { project: &ModelHandle, basis: usize, theme: &Theme, - follower_state: &FollowerStatesByLeader, + follower_states: &HashMap, FollowerState>, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, zoomed: Option<&AnyViewHandle>, @@ -515,7 +504,7 @@ impl PaneAxis { project, (basis + ix) * 10, theme, - follower_state, + follower_states, active_call, active_pane, zoomed, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d39c1cce76..d97e444eb6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -574,7 +574,7 @@ pub struct Workspace { titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: ModelHandle, - follower_states_by_leader: FollowerStatesByLeader, + follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, active_call: Option<(ModelHandle, Vec)>, @@ -599,10 +599,9 @@ pub struct ViewId { pub id: u64, } -type FollowerStatesByLeader = HashMap, FollowerState>>; - #[derive(Default)] struct FollowerState { + leader_id: PeerId, active_view_id: Option, items_by_leader_view_id: HashMap>, } @@ -791,7 +790,7 @@ impl Workspace { bottom_dock, right_dock, project: project.clone(), - follower_states_by_leader: Default::default(), + follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, active_call, @@ -2509,13 +2508,16 @@ impl Workspace { } fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext) { - if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) { - for state in states_by_pane.into_values() { - for item in state.items_by_leader_view_id.into_values() { + self.follower_states.retain(|_, state| { + if state.leader_id == peer_id { + for item in state.items_by_leader_view_id.values() { item.set_leader_peer_id(None, cx); } + false + } else { + true } - } + }); cx.notify(); } @@ -2528,10 +2530,15 @@ impl Workspace { self.last_leaders_by_pane .insert(pane.downgrade(), leader_id); - self.follower_states_by_leader - .entry(leader_id) - .or_default() - .insert(pane.clone(), Default::default()); + self.unfollow(&pane, cx); + self.follower_states.insert( + pane.clone(), + FollowerState { + leader_id, + active_view_id: None, + items_by_leader_view_id: Default::default(), + }, + ); cx.notify(); let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); @@ -2546,9 +2553,8 @@ impl Workspace { let response = request.await?; this.update(&mut cx, |this, _| { let state = this - .follower_states_by_leader - .get_mut(&leader_id) - .and_then(|states_by_pane| states_by_pane.get_mut(&pane)) + .follower_states + .get_mut(&pane) .ok_or_else(|| anyhow!("following interrupted"))?; state.active_view_id = if let Some(active_view_id) = response.active_view_id { Some(ViewId::from_proto(active_view_id)?) @@ -2643,12 +2649,10 @@ impl Workspace { } // if you're already following, find the right pane and focus it. - for (existing_leader_id, states_by_pane) in &mut self.follower_states_by_leader { - if leader_id == *existing_leader_id { - for (pane, _) in states_by_pane { - cx.focus(pane); - return None; - } + for (pane, state) in &self.follower_states { + if leader_id == state.leader_id { + cx.focus(pane); + return None; } } @@ -2661,36 +2665,37 @@ impl Workspace { pane: &ViewHandle, cx: &mut ViewContext, ) -> Option { - for (leader_id, states_by_pane) in &mut self.follower_states_by_leader { - let leader_id = *leader_id; - if let Some(state) = states_by_pane.remove(pane) { - for (_, item) in state.items_by_leader_view_id { - item.set_leader_peer_id(None, cx); - } - - if states_by_pane.is_empty() { - self.follower_states_by_leader.remove(&leader_id); - let project_id = self.project.read(cx).remote_id(); - let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); - self.app_state - .client - .send(proto::Unfollow { - room_id, - project_id, - leader_id: Some(leader_id), - }) - .log_err(); - } - - cx.notify(); - return Some(leader_id); - } + let state = self.follower_states.remove(pane)?; + let leader_id = state.leader_id; + for (_, item) in state.items_by_leader_view_id { + item.set_leader_peer_id(None, cx); } - None + + if self + .follower_states + .values() + .all(|state| state.leader_id != state.leader_id) + { + let project_id = self.project.read(cx).remote_id(); + let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); + self.app_state + .client + .send(proto::Unfollow { + room_id, + project_id, + leader_id: Some(leader_id), + }) + .log_err(); + } + + cx.notify(); + Some(leader_id) } pub fn is_being_followed(&self, peer_id: PeerId) -> bool { - self.follower_states_by_leader.contains_key(&peer_id) + self.follower_states + .values() + .any(|state| state.leader_id == peer_id) } fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { @@ -2913,8 +2918,8 @@ impl Workspace { match update.variant.ok_or_else(|| anyhow!("invalid update"))? { proto::update_followers::Variant::UpdateActiveView(update_active_view) => { this.update(cx, |this, _| { - if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) { - for state in state.values_mut() { + for (_, state) in &mut this.follower_states { + if state.leader_id == leader_id { state.active_view_id = if let Some(active_view_id) = update_active_view.id.clone() { Some(ViewId::from_proto(active_view_id)?) @@ -2936,8 +2941,8 @@ impl Workspace { let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); - if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) { - for state in state.values_mut() { + for (_, state) in &mut this.follower_states { + if state.leader_id == leader_id { let view_id = ViewId::from_proto(id.clone())?; if let Some(item) = state.items_by_leader_view_id.get(&view_id) { tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); @@ -2950,10 +2955,9 @@ impl Workspace { } proto::update_followers::Variant::CreateView(view) => { let panes = this.read_with(cx, |this, _| { - this.follower_states_by_leader - .get(&leader_id) - .into_iter() - .flat_map(|states_by_pane| states_by_pane.keys()) + this.follower_states + .iter() + .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane)) .cloned() .collect() })?; @@ -3012,11 +3016,7 @@ impl Workspace { for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane { let items = futures::future::try_join_all(item_tasks).await?; this.update(cx, |this, cx| { - let state = this - .follower_states_by_leader - .get_mut(&leader_id)? - .get_mut(&pane)?; - + let state = this.follower_states.get_mut(&pane)?; for (id, item) in leader_view_ids.into_iter().zip(items) { item.set_leader_peer_id(Some(leader_id), cx); state.items_by_leader_view_id.insert(id, item); @@ -3073,15 +3073,7 @@ impl Workspace { } pub fn leader_for_pane(&self, pane: &ViewHandle) -> Option { - self.follower_states_by_leader - .iter() - .find_map(|(leader_id, state)| { - if state.contains_key(pane) { - Some(*leader_id) - } else { - None - } - }) + self.follower_states.get(pane).map(|state| state.leader_id) } fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { @@ -3109,7 +3101,10 @@ impl Workspace { } }; - for (pane, state) in self.follower_states_by_leader.get(&leader_id)? { + for (pane, state) in &self.follower_states { + if state.leader_id != leader_id { + continue; + } if leader_in_this_app { let item = state .active_view_id @@ -3804,7 +3799,7 @@ impl View for Workspace { self.center.render( &project, &theme, - &self.follower_states_by_leader, + &self.follower_states, self.active_call(), self.active_pane(), self.zoomed From ba4f4e0a3e1806d4f44e0732f14f93620b86790b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 23:38:24 +0300 Subject: [PATCH 052/141] Detect file paths that end with `:` New rustc messages look like ``` thread 'tests::test_history_items_vs_very_good_external_match' panicked at crates/file_finder/src/file_finder.rs:1902:13: assertion `left == right` failed: Only one history item contains collab_ui, it should be present and others should be filtered out left: 0 right: 1 ``` now and we fail to parse that `13:` bit properly, fix that. --- crates/util/src/paths.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index e7e6e0ac72..4578ce0bc9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -139,6 +139,12 @@ impl

PathLikeWithPosition

{ column: None, }) } else { + let maybe_col_str = + if maybe_col_str.ends_with(FILE_ROW_COLUMN_DELIMITER) { + &maybe_col_str[..maybe_col_str.len() - 1] + } else { + maybe_col_str + }; match maybe_col_str.parse::() { Ok(col) => Ok(Self { path_like: parse_path_like_str(path_like_str)?, @@ -241,7 +247,6 @@ mod tests { "test_file.rs:1::", "test_file.rs::1:2", "test_file.rs:1::2", - "test_file.rs:1:2:", "test_file.rs:1:2:3", ] { let actual = parse_str(input); @@ -277,6 +282,14 @@ mod tests { column: None, }, ), + ( + "crates/file_finder/src/file_finder.rs:1902:13:", + PathLikeWithPosition { + path_like: "crates/file_finder/src/file_finder.rs".to_string(), + row: Some(1902), + column: Some(13), + }, + ), ]; for (input, expected) in input_and_expected { From 90b54a45e850d038c8a0f4767f50eb2fb3e62706 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 14:29:45 -0700 Subject: [PATCH 053/141] Log a warning when leader activates an unknown view --- crates/workspace/src/workspace.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d97e444eb6..bf96558f5c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3105,16 +3105,19 @@ impl Workspace { if state.leader_id != leader_id { continue; } - if leader_in_this_app { - let item = state - .active_view_id - .and_then(|id| state.items_by_leader_view_id.get(&id)); - if let Some(item) = item { + if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) { + if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) { if leader_in_this_project || !item.is_project_item(cx) { items_to_activate.push((pane.clone(), item.boxed_clone())); } - continue; + } else { + log::warn!( + "unknown view id {:?} for leader {:?}", + active_view_id, + leader_id + ); } + continue; } if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { items_to_activate.push((pane.clone(), Box::new(shared_screen))); From b807b3c7852e717c7d7ac59c7c6a50083682229f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 14:45:19 -0700 Subject: [PATCH 054/141] Handle participants' participant index changing This normally doesn't happen, but it can happen if a participant loses connection ungracefully, restarts their app, and then explicitly joins again. --- crates/call/src/room.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 72db174d72..2fb1cab40e 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -686,6 +686,7 @@ impl Room { let Some(peer_id) = participant.peer_id else { continue; }; + let participant_index = ParticipantIndex(participant.participant_index); this.participant_user_ids.insert(participant.user_id); let old_projects = this @@ -736,8 +737,9 @@ impl Room { if let Some(remote_participant) = this.remote_participants.get_mut(&participant.user_id) { - remote_participant.projects = participant.projects; remote_participant.peer_id = peer_id; + remote_participant.projects = participant.projects; + remote_participant.participant_index = participant_index; if location != remote_participant.location { remote_participant.location = location; cx.emit(Event::ParticipantLocationChanged { @@ -749,9 +751,7 @@ impl Room { participant.user_id, RemoteParticipant { user: user.clone(), - participant_index: ParticipantIndex( - participant.participant_index, - ), + participant_index, peer_id, projects: participant.projects, location, From bdcbf9b92eb637e1c4d4b00fb610da4845202d8b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 14:46:29 -0700 Subject: [PATCH 055/141] Add a Reconnect action, for simulating connection blips --- crates/client/src/client.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 5767ac54b7..9f63d0e2be 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -70,7 +70,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894"; pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100); pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); -actions!(client, [SignIn, SignOut]); +actions!(client, [SignIn, SignOut, Reconnect]); pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); @@ -102,6 +102,17 @@ pub fn init(client: &Arc, cx: &mut AppContext) { } } }); + cx.add_global_action({ + let client = client.clone(); + move |_: &Reconnect, cx| { + if let Some(client) = client.upgrade() { + cx.spawn(|cx| async move { + client.reconnect(&cx); + }) + .detach(); + } + } + }); } pub struct Client { @@ -1212,6 +1223,11 @@ impl Client { self.set_status(Status::SignedOut, cx); } + pub fn reconnect(self: &Arc, cx: &AsyncAppContext) { + self.peer.teardown(); + self.set_status(Status::ConnectionLost, cx); + } + fn connection_id(&self) -> Result { if let Status::Connected { connection_id, .. } = *self.status().borrow() { Ok(connection_id) From 1d29709c3232c40a51dd2397c2d9b0b4086453af Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Oct 2023 15:04:01 -0700 Subject: [PATCH 056/141] Avoid possible panic in Room::most_active_project Participants' locations might momentarily reference projects that have already been unshared. --- crates/call/src/room.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 2fb1cab40e..43354fd5a2 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -600,27 +600,30 @@ impl Room { /// Returns the most 'active' projects, defined as most people in the project pub fn most_active_project(&self) -> Option<(u64, u64)> { - let mut projects = HashMap::default(); - let mut hosts = HashMap::default(); + let mut project_hosts_and_guest_counts = HashMap::, u32)>::default(); for participant in self.remote_participants.values() { match participant.location { ParticipantLocation::SharedProject { project_id } => { - *projects.entry(project_id).or_insert(0) += 1; + project_hosts_and_guest_counts + .entry(project_id) + .or_default() + .1 += 1; } ParticipantLocation::External | ParticipantLocation::UnsharedProject => {} } for project in &participant.projects { - *projects.entry(project.id).or_insert(0) += 1; - hosts.insert(project.id, participant.user.id); + project_hosts_and_guest_counts + .entry(project.id) + .or_default() + .0 = Some(participant.user.id); } } - let mut pairs: Vec<(u64, usize)> = projects.into_iter().collect(); - pairs.sort_by_key(|(_, count)| *count as i32); - - pairs - .first() - .map(|(project_id, _)| (*project_id, hosts[&project_id])) + project_hosts_and_guest_counts + .into_iter() + .filter_map(|(id, (host, guest_count))| Some((id, host?, guest_count))) + .max_by_key(|(_, _, guest_count)| *guest_count) + .map(|(id, host, _)| (id, host)) } async fn handle_room_updated( From 8922437fcd0ae13ab6e3ba23cdac071ff20e7f2a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 9 Oct 2023 18:13:52 -0600 Subject: [PATCH 057/141] code review --- crates/call/src/room.rs | 2 +- crates/workspace/src/workspace.rs | 14 ++------------ crates/zed/Cargo.toml | 2 +- crates/zed/resources/zed.entitlements | 12 ++++++++---- crates/zed/src/main.rs | 13 ++----------- crates/zed/src/{open_url.rs => open_listener.rs} | 10 ---------- 6 files changed, 14 insertions(+), 39 deletions(-) rename crates/zed/src/{open_url.rs => open_listener.rs} (92%) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index b7aeee90e2..32b8232b4f 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -878,7 +878,7 @@ impl Room { Ok(()) } - pub fn next_room_update(&mut self) -> impl Future { + pub fn room_update_completed(&mut self) -> impl Future { let mut done_rx = self.room_update_completed_rx.clone(); async move { while let Some(result) = done_rx.next().await { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e690757af7..c021384d91 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,7 +47,6 @@ use std::{ any::TypeId, borrow::Cow, cmp, env, - fs::OpenOptions, future::Future, path::{Path, PathBuf}, rc::Rc, @@ -4246,7 +4245,8 @@ async fn join_channel_internal( }) .await?; - room.update(cx, |room, _| room.next_room_update()).await; + room.update(cx, |room, _| room.room_update_completed()) + .await; let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project(cx) { @@ -4259,16 +4259,6 @@ async fn join_channel_internal( task.await?; return anyhow::Ok(true); } - use std::io::Write; - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "no jokes" - ) - .unwrap(); anyhow::Ok(false) } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 3c93462d4b..7eb14559be 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -156,7 +156,7 @@ workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true -[package.metadata.bundle] +[package.metadata.bundle-dev] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] identifier = "dev.zed.Zed-Dev" name = "Zed Dev" diff --git a/crates/zed/resources/zed.entitlements b/crates/zed/resources/zed.entitlements index 9f5414243d..f40a8a253a 100644 --- a/crates/zed/resources/zed.entitlements +++ b/crates/zed/resources/zed.entitlements @@ -2,8 +2,6 @@ - com.apple.developer.associated-domains - applinks:cirw.in com.apple.security.automation.apple-events com.apple.security.cs.allow-jit @@ -12,8 +10,14 @@ com.apple.security.device.camera - com.apple.security.keychain-access-groups - MQ55VZLNZQ.dev.zed.Shared + com.apple.security.personal-information.addressbook + + com.apple.security.personal-information.calendars + + com.apple.security.personal-information.location + + com.apple.security.personal-information.photos-library + diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 004e5769d5..a75caa54f6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -61,20 +61,11 @@ use zed::{ only_instance::{ensure_only_instance, IsOnlyInstance}, }; -use crate::open_url::{OpenListener, OpenRequest}; +use crate::open_listener::{OpenListener, OpenRequest}; -mod open_url; +mod open_listener; fn main() { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "HELLO" - ) - .unwrap(); let http = http::client(); init_paths(); init_logger(); diff --git a/crates/zed/src/open_url.rs b/crates/zed/src/open_listener.rs similarity index 92% rename from crates/zed/src/open_url.rs rename to crates/zed/src/open_listener.rs index 8cad217a8c..e3c08ff2c8 100644 --- a/crates/zed/src/open_url.rs +++ b/crates/zed/src/open_listener.rs @@ -43,16 +43,6 @@ impl OpenListener { } pub fn open_urls(&self, urls: Vec) { - writeln!( - OpenOptions::new() - .write(true) - .append(true) - .open("/Users/conrad/dbg") - .unwrap(), - "{:?}", - &urls, - ) - .unwrap(); self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) From dcdd74dff43b421acb2526a6c9592cde7ad78cf0 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 10 Oct 2023 00:00:57 -0400 Subject: [PATCH 058/141] Truncate Discord release note text --- .github/workflows/release_actions.yml | 29 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index f767324e4f..54e5d07787 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -10,19 +10,30 @@ jobs: id: get-appropriate-url run: | if [ "${{ github.event.release.prerelease }}" == "true" ]; then - URL="https://zed.dev/releases/preview/latest" + url="https://zed.dev/releases/preview/latest" else - URL="https://zed.dev/releases/stable/latest" + url="https://zed.dev/releases/stable/latest" fi - echo "::set-output name=URL::$URL" + echo "::set-output name=url::$url" + + - name: Prepare release content + id: prepare-content + run: | + set -eu + + text="📣 Zed ${{ github.event.release.tag_name }} was just released!\n\nRestart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.\n\n${{ github.event.release.body }}" + + maxTextLength=2000 + truncationIndicator="..." + + if (( ${#text} > maxTextLength )); then + text=${text:0:maxTextLength - ${#truncationIndicator}}$truncationIndicator + fi + + echo "::set-output name=content::$text" - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: | - 📣 Zed ${{ github.event.release.tag_name }} was just released! - - Restart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it. - - ${{ github.event.release.body }} + content: ${{ steps.prepare-content.outputs.content }} From 354882f2c00d877defaf050ae8a34451ef5fa851 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 00:16:15 -0400 Subject: [PATCH 059/141] Enable completion menu to resolve documentation when guest --- crates/collab/src/rpc.rs | 1 + crates/editor/src/editor.rs | 74 ++++++++++++-- crates/language/src/buffer.rs | 13 +-- crates/project/src/lsp_command.rs | 12 ++- crates/project/src/project.rs | 35 +++++++ crates/rpc/proto/zed.proto | 157 ++++++++++++++++-------------- crates/rpc/src/proto.rs | 7 ++ crates/rpc/src/rpc.rs | 2 +- 8 files changed, 207 insertions(+), 94 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5eb434e167..ed09cde061 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -224,6 +224,7 @@ impl Server { .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b8c9690b90..3143b19629 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -60,10 +60,10 @@ use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, - point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, - CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize, - Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, - TransactionId, + markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, + Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, + IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, + SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -80,7 +80,7 @@ use ordered_float::OrderedFloat; use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::{seq::SliceRandom, thread_rng}; -use rpc::proto::PeerId; +use rpc::proto::{self, PeerId}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -999,7 +999,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); let completion = &completions_guard[index]; - if completion.lsp_completion.documentation.is_some() { + if completion.documentation.is_some() { return; } @@ -1007,6 +1007,57 @@ impl CompletionsMenu { 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 request = proto::ResolveCompletionDocumentation { + project_id, + language_server_id: server_id.0 as u64, + lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + }; + + cx.spawn(|this, mut cx| async move { + 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[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[index]; + completion.documentation = Some(documentation); + drop(completions); + + _ = this.update(&mut cx, |_, cx| cx.notify()); + }) + .detach(); + + return; + } + let Some(server) = project.read(cx).language_server_for_id(server_id) else { return; }; @@ -1037,11 +1088,14 @@ impl CompletionsMenu { let mut completions = completions.write(); let completion = &mut completions[index]; - completion.documentation = documentation; - completion.lsp_completion.documentation = Some(lsp_documentation); + completion.documentation = Some(documentation); drop(completions); _ = this.update(&mut cx, |_, cx| cx.notify()); + } else { + let mut completions = completions.write(); + let completion = &mut completions[index]; + completion.documentation = Some(Documentation::Undocumented); } }) .detach(); @@ -1061,10 +1115,10 @@ impl CompletionsMenu { .max_by_key(|(_, mat)| { let completions = self.completions.read(); let completion = &completions[mat.candidate_id]; - let documentation = &completion.lsp_completion.documentation; + let documentation = &completion.documentation; let mut len = completion.label.text.chars().count(); - if let Some(lsp::Documentation::String(text)) = documentation { + if let Some(Documentation::SingleLine(text)) = documentation { len += text.chars().count(); } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d318a87b40..d8ebc1d445 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -149,28 +149,28 @@ pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, language: Option>, -) -> Option { +) -> Documentation { match documentation { lsp::Documentation::String(text) => { if text.lines().count() <= 1 { - Some(Documentation::SingleLine(text.clone())) + Documentation::SingleLine(text.clone()) } else { - Some(Documentation::MultiLinePlainText(text.clone())) + Documentation::MultiLinePlainText(text.clone()) } } lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { lsp::MarkupKind::PlainText => { if value.lines().count() <= 1 { - Some(Documentation::SingleLine(value.clone())) + Documentation::SingleLine(value.clone()) } else { - Some(Documentation::MultiLinePlainText(value.clone())) + Documentation::MultiLinePlainText(value.clone()) } } lsp::MarkupKind::Markdown => { let parsed = parse_markdown(value, language_registry, language).await; - Some(Documentation::MultiLineMarkdown(parsed)) + Documentation::MultiLineMarkdown(parsed) } }, } @@ -178,6 +178,7 @@ pub async fn prepare_completion_documentation( #[derive(Clone, Debug)] pub enum Documentation { + Undocumented, SingleLine(String), MultiLinePlainText(String), MultiLineMarkdown(ParsedMarkdown), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index c71b378da6..72d79ca979 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1466,12 +1466,14 @@ impl LspCommand for GetCompletions { } let documentation = if let Some(lsp_docs) = &lsp_completion.documentation { - prepare_completion_documentation( - lsp_docs, - &language_registry, - language.clone(), + Some( + prepare_completion_documentation( + lsp_docs, + &language_registry, + language.clone(), + ) + .await, ) - .await } else { None }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631..f25309b9c6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -580,6 +580,7 @@ impl Project { client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); client.add_model_request_handler(Self::handle_inlay_hints); + client.add_model_request_handler(Self::handle_resolve_completion_documentation); client.add_model_request_handler(Self::handle_resolve_inlay_hint); client.add_model_request_handler(Self::handle_refresh_inlay_hints); client.add_model_request_handler(Self::handle_reload_buffers); @@ -7155,6 +7156,40 @@ impl Project { }) } + async fn handle_resolve_completion_documentation( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?; + + let completion = this + .read_with(&mut cx, |this, _| { + let id = LanguageServerId(envelope.payload.language_server_id as usize); + let Some(server) = this.language_server_for_id(id) else { + return Err(anyhow!("No language server {id}")); + }; + + Ok(server.request::(lsp_completion)) + })? + .await?; + + let mut is_markdown = false; + let text = match completion.documentation { + Some(lsp::Documentation::String(text)) => text, + + Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => { + is_markdown = kind == lsp::MarkupKind::Markdown; + value + } + + _ => String::new(), + }; + + Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown }) + } + async fn handle_apply_code_action( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6a..e97ede3fee 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -89,88 +89,90 @@ message Envelope { FormatBuffersResponse format_buffers_response = 70; GetCompletions get_completions = 71; GetCompletionsResponse get_completions_response = 72; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74; - GetCodeActions get_code_actions = 75; - GetCodeActionsResponse get_code_actions_response = 76; - GetHover get_hover = 77; - GetHoverResponse get_hover_response = 78; - ApplyCodeAction apply_code_action = 79; - ApplyCodeActionResponse apply_code_action_response = 80; - PrepareRename prepare_rename = 81; - PrepareRenameResponse prepare_rename_response = 82; - PerformRename perform_rename = 83; - PerformRenameResponse perform_rename_response = 84; - SearchProject search_project = 85; - SearchProjectResponse search_project_response = 86; + ResolveCompletionDocumentation resolve_completion_documentation = 73; + ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76; + GetCodeActions get_code_actions = 77; + GetCodeActionsResponse get_code_actions_response = 78; + GetHover get_hover = 79; + GetHoverResponse get_hover_response = 80; + ApplyCodeAction apply_code_action = 81; + ApplyCodeActionResponse apply_code_action_response = 82; + PrepareRename prepare_rename = 83; + PrepareRenameResponse prepare_rename_response = 84; + PerformRename perform_rename = 85; + PerformRenameResponse perform_rename_response = 86; + SearchProject search_project = 87; + SearchProjectResponse search_project_response = 88; - UpdateContacts update_contacts = 87; - UpdateInviteInfo update_invite_info = 88; - ShowContacts show_contacts = 89; + UpdateContacts update_contacts = 89; + UpdateInviteInfo update_invite_info = 90; + ShowContacts show_contacts = 91; - GetUsers get_users = 90; - FuzzySearchUsers fuzzy_search_users = 91; - UsersResponse users_response = 92; - RequestContact request_contact = 93; - RespondToContactRequest respond_to_contact_request = 94; - RemoveContact remove_contact = 95; + GetUsers get_users = 92; + FuzzySearchUsers fuzzy_search_users = 93; + UsersResponse users_response = 94; + RequestContact request_contact = 95; + RespondToContactRequest respond_to_contact_request = 96; + RemoveContact remove_contact = 97; - Follow follow = 96; - FollowResponse follow_response = 97; - UpdateFollowers update_followers = 98; - Unfollow unfollow = 99; - GetPrivateUserInfo get_private_user_info = 100; - GetPrivateUserInfoResponse get_private_user_info_response = 101; - UpdateDiffBase update_diff_base = 102; + Follow follow = 98; + FollowResponse follow_response = 99; + UpdateFollowers update_followers = 100; + Unfollow unfollow = 101; + GetPrivateUserInfo get_private_user_info = 102; + GetPrivateUserInfoResponse get_private_user_info_response = 103; + UpdateDiffBase update_diff_base = 104; - OnTypeFormatting on_type_formatting = 103; - OnTypeFormattingResponse on_type_formatting_response = 104; + OnTypeFormatting on_type_formatting = 105; + OnTypeFormattingResponse on_type_formatting_response = 106; - UpdateWorktreeSettings update_worktree_settings = 105; + UpdateWorktreeSettings update_worktree_settings = 107; - InlayHints inlay_hints = 106; - InlayHintsResponse inlay_hints_response = 107; - ResolveInlayHint resolve_inlay_hint = 108; - ResolveInlayHintResponse resolve_inlay_hint_response = 109; - RefreshInlayHints refresh_inlay_hints = 110; + InlayHints inlay_hints = 108; + InlayHintsResponse inlay_hints_response = 109; + ResolveInlayHint resolve_inlay_hint = 110; + ResolveInlayHintResponse resolve_inlay_hint_response = 111; + RefreshInlayHints refresh_inlay_hints = 112; - CreateChannel create_channel = 111; - CreateChannelResponse create_channel_response = 112; - InviteChannelMember invite_channel_member = 113; - RemoveChannelMember remove_channel_member = 114; - RespondToChannelInvite respond_to_channel_invite = 115; - UpdateChannels update_channels = 116; - JoinChannel join_channel = 117; - DeleteChannel delete_channel = 118; - GetChannelMembers get_channel_members = 119; - GetChannelMembersResponse get_channel_members_response = 120; - SetChannelMemberAdmin set_channel_member_admin = 121; - RenameChannel rename_channel = 122; - RenameChannelResponse rename_channel_response = 123; + CreateChannel create_channel = 113; + CreateChannelResponse create_channel_response = 114; + InviteChannelMember invite_channel_member = 115; + RemoveChannelMember remove_channel_member = 116; + RespondToChannelInvite respond_to_channel_invite = 117; + UpdateChannels update_channels = 118; + JoinChannel join_channel = 119; + DeleteChannel delete_channel = 120; + GetChannelMembers get_channel_members = 121; + GetChannelMembersResponse get_channel_members_response = 122; + SetChannelMemberAdmin set_channel_member_admin = 123; + RenameChannel rename_channel = 124; + RenameChannelResponse rename_channel_response = 125; - JoinChannelBuffer join_channel_buffer = 124; - JoinChannelBufferResponse join_channel_buffer_response = 125; - UpdateChannelBuffer update_channel_buffer = 126; - LeaveChannelBuffer leave_channel_buffer = 127; - UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128; - RejoinChannelBuffers rejoin_channel_buffers = 129; - RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130; - AckBufferOperation ack_buffer_operation = 143; + JoinChannelBuffer join_channel_buffer = 126; + JoinChannelBufferResponse join_channel_buffer_response = 127; + UpdateChannelBuffer update_channel_buffer = 128; + LeaveChannelBuffer leave_channel_buffer = 129; + UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130; + RejoinChannelBuffers rejoin_channel_buffers = 131; + RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132; + AckBufferOperation ack_buffer_operation = 145; - JoinChannelChat join_channel_chat = 131; - JoinChannelChatResponse join_channel_chat_response = 132; - LeaveChannelChat leave_channel_chat = 133; - SendChannelMessage send_channel_message = 134; - SendChannelMessageResponse send_channel_message_response = 135; - ChannelMessageSent channel_message_sent = 136; - GetChannelMessages get_channel_messages = 137; - GetChannelMessagesResponse get_channel_messages_response = 138; - RemoveChannelMessage remove_channel_message = 139; - AckChannelMessage ack_channel_message = 144; + JoinChannelChat join_channel_chat = 133; + JoinChannelChatResponse join_channel_chat_response = 134; + LeaveChannelChat leave_channel_chat = 135; + SendChannelMessage send_channel_message = 136; + SendChannelMessageResponse send_channel_message_response = 137; + ChannelMessageSent channel_message_sent = 138; + GetChannelMessages get_channel_messages = 139; + GetChannelMessagesResponse get_channel_messages_response = 140; + RemoveChannelMessage remove_channel_message = 141; + AckChannelMessage ack_channel_message = 146; - LinkChannel link_channel = 140; - UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + LinkChannel link_channel = 142; + UnlinkChannel unlink_channel = 143; + MoveChannel move_channel = 144; // current max: 146 } } @@ -832,6 +834,17 @@ message ResolveState { } } +message ResolveCompletionDocumentation { + uint64 project_id = 1; + uint64 language_server_id = 2; + bytes lsp_completion = 3; +} + +message ResolveCompletionDocumentationResponse { + string text = 1; + bool is_markdown = 2; +} + message ResolveInlayHint { uint64 project_id = 1; uint64 buffer_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f..abadada328 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -205,6 +205,8 @@ messages!( (OnTypeFormattingResponse, Background), (InlayHints, Background), (InlayHintsResponse, Background), + (ResolveCompletionDocumentation, Background), + (ResolveCompletionDocumentationResponse, Background), (ResolveInlayHint, Background), (ResolveInlayHintResponse, Background), (RefreshInlayHints, Foreground), @@ -318,6 +320,10 @@ request_messages!( (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), (InlayHints, InlayHintsResponse), + ( + ResolveCompletionDocumentation, + ResolveCompletionDocumentationResponse + ), (ResolveInlayHint, ResolveInlayHintResponse), (RefreshInlayHints, Ack), (ReloadBuffers, ReloadBuffersResponse), @@ -381,6 +387,7 @@ entity_messages!( PerformRename, OnTypeFormatting, InlayHints, + ResolveCompletionDocumentation, ResolveInlayHint, RefreshInlayHints, PrepareRename, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 942672b94b..5ba531a50e 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 64; +pub const PROTOCOL_VERSION: u32 = 65; From 639ae671aed28847af279df03ac77f6825672027 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Oct 2023 12:26:48 +0300 Subject: [PATCH 060/141] Omit history files with path that does not exist on disk anymore --- crates/file_finder/src/file_finder.rs | 122 +++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 222a9c650a..b7a4a387ab 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -222,6 +222,10 @@ fn toggle_or_cycle_file_finder( .as_ref() .and_then(|found_path| found_path.absolute.as_ref()) }) + .filter(|(_, history_abs_path)| match history_abs_path { + Some(abs_path) => history_file_exists(abs_path), + None => true, + }) .map(|(history_path, abs_path)| FoundPath::new(history_path, abs_path)), ) .collect(); @@ -246,6 +250,16 @@ fn toggle_or_cycle_file_finder( } } +#[cfg(not(test))] +fn history_file_exists(abs_path: &PathBuf) -> bool { + abs_path.exists() +} + +#[cfg(test)] +fn history_file_exists(abs_path: &PathBuf) -> bool { + !abs_path.ends_with("nonexistent.rs") +} + pub enum Event { Selected(ProjectPath), Dismissed, @@ -515,12 +529,7 @@ impl PickerDelegate for FileFinderDelegate { project .worktree_for_id(history_item.project.worktree_id, cx) .is_some() - || (project.is_local() - && history_item - .absolute - .as_ref() - .filter(|abs_path| abs_path.exists()) - .is_some()) + || (project.is_local() && history_item.absolute.is_some()) }) .cloned() .map(|p| (p, None)) @@ -1900,13 +1909,8 @@ mod tests { .matches .search .iter() - .map(|e| e.path.to_path_buf()) + .map(|path_match| path_match.path.to_path_buf()) .collect::>(); - assert_eq!( - search_entries.len(), - 4, - "All history and the new file should be found after query {query} as search results" - ); assert_eq!( search_entries, vec![ @@ -1920,6 +1924,100 @@ mod tests { }); } + #[gpui::test] + async fn test_nonexistent_history_items_not_shown( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "nonexistent.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.root(cx); + // generate some history to select from + open_close_queried_buffer( + "fir", + 1, + "first.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "non", + 1, + "nonexistent.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "thi", + 1, + "third.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + open_close_queried_buffer( + "fir", + 1, + "first.rs", + window.into(), + &workspace, + &deterministic, + cx, + ) + .await; + + cx.dispatch_action(window.into(), Toggle); + let query = "rs"; + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches(query.to_string(), cx) + }) + .await; + finder.read_with(cx, |finder, _| { + let delegate = finder.delegate(); + let history_entries = delegate + .matches + .history + .iter() + .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf()) + .collect::>(); + assert_eq!( + history_entries, + vec![ + PathBuf::from("test/first.rs"), + PathBuf::from("test/third.rs"), + ], + "Should have all opened files in the history, except the ones that do not exist on disk" + ); + }); + } + async fn open_close_queried_buffer( input: &str, expected_matches: usize, From f5af5f7334a7b523090c0741029dde913cd00c2c Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 09:27:18 -0400 Subject: [PATCH 061/141] Avoid leaving selected item index past end of matches list Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3143b19629..9b276f002c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1332,6 +1332,7 @@ impl CompletionsMenu { } self.matches = matches.into(); + self.selected_item = 0; } } From 801af95a13e0332af3f9a4d1a1d62c47bea07529 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 10 Oct 2023 10:08:29 -0400 Subject: [PATCH 062/141] Make completion documentation scroll & fix accompanying panic from tag Co-Authored-By: Antonio Scandurra --- crates/editor/src/editor.rs | 26 +++++++---- crates/editor/src/hover_popover.rs | 4 +- crates/gpui/src/elements/flex.rs | 75 ++++++++++++++++++------------ 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9b276f002c..06482dbbc6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -119,7 +119,7 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub fn render_parsed_markdown( +pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, cx: &mut ViewContext, @@ -153,7 +153,7 @@ pub fn render_parsed_markdown( style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( - MouseRegion::new::(view_id, region_id, bounds) + MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) .on_click::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), @@ -1247,6 +1247,8 @@ impl CompletionsMenu { }) .with_width_from_item(widest_completion_ix); + enum MultiLineDocumentation {} + Flex::row() .with_child(list) .with_children({ @@ -1256,13 +1258,21 @@ impl CompletionsMenu { let documentation = &completion.documentation; match documentation { - Some(Documentation::MultiLinePlainText(text)) => { - Some(Text::new(text.clone(), style.text.clone())) - } + Some(Documentation::MultiLinePlainText(text)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child( + Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), + ), + ), - Some(Documentation::MultiLineMarkdown(parsed)) => { - Some(render_parsed_markdown(parsed, &style, cx)) - } + Some(Documentation::MultiLineMarkdown(parsed)) => Some( + Flex::column() + .scrollable::(0, None, cx) + .with_child(render_parsed_markdown::( + parsed, &style, cx, + )), + ), _ => None, } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index d5ccb481b2..e8901ad6c1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -474,8 +474,8 @@ impl InfoPopover { ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { Flex::column() - .scrollable::(1, None, cx) - .with_child(crate::render_parsed_markdown( + .scrollable::(0, None, cx) + .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, cx, diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index cdce0423fd..ba387c5e48 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -2,7 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc}; use crate::{ json::{self, ToJson, Value}, - AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext, + AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt, + ViewContext, }; use pathfinder_geometry::{ rect::RectF, @@ -10,10 +11,10 @@ use pathfinder_geometry::{ }; use serde_json::json; -#[derive(Default)] struct ScrollState { scroll_to: Cell>, scroll_position: Cell, + type_tag: TypeTag, } pub struct Flex { @@ -66,8 +67,14 @@ impl Flex { where Tag: 'static, { - let scroll_state = cx.default_element_state::>(element_id); - scroll_state.read(cx).scroll_to.set(scroll_to); + let scroll_state = cx.element_state::>( + element_id, + Rc::new(ScrollState { + scroll_to: Cell::new(scroll_to), + scroll_position: Default::default(), + type_tag: TypeTag::new::(), + }), + ); self.scroll_state = Some((scroll_state, cx.handle().id())); self } @@ -276,38 +283,44 @@ impl Element for Flex { if let Some((scroll_state, id)) = &self.scroll_state { let scroll_state = scroll_state.read(cx).clone(); cx.scene().push_mouse_region( - crate::MouseRegion::new::(*id, 0, bounds) - .on_scroll({ - let axis = self.axis; - move |e, _: &mut V, cx| { - if remaining_space < 0. { - let scroll_delta = e.delta.raw(); + crate::MouseRegion::from_handlers( + scroll_state.type_tag, + *id, + 0, + bounds, + Default::default(), + ) + .on_scroll({ + let axis = self.axis; + move |e, _: &mut V, cx| { + if remaining_space < 0. { + let scroll_delta = e.delta.raw(); - let mut delta = match axis { - Axis::Horizontal => { - if scroll_delta.x().abs() >= scroll_delta.y().abs() { - scroll_delta.x() - } else { - scroll_delta.y() - } + let mut delta = match axis { + Axis::Horizontal => { + if scroll_delta.x().abs() >= scroll_delta.y().abs() { + scroll_delta.x() + } else { + scroll_delta.y() } - Axis::Vertical => scroll_delta.y(), - }; - if !e.delta.precise() { - delta *= 20.; } - - scroll_state - .scroll_position - .set(scroll_state.scroll_position.get() - delta); - - cx.notify(); - } else { - cx.propagate_event(); + Axis::Vertical => scroll_delta.y(), + }; + if !e.delta.precise() { + delta *= 20.; } + + scroll_state + .scroll_position + .set(scroll_state.scroll_position.get() - delta); + + cx.notify(); + } else { + cx.propagate_event(); } - }) - .on_move(|_, _: &mut V, _| { /* Capture move events */ }), + } + }) + .on_move(|_, _: &mut V, _| { /* Capture move events */ }), ) } From 5cf92980f0d7a4e6320bb172b7115ad59dfb25e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 10 Oct 2023 17:51:17 +0200 Subject: [PATCH 063/141] Revert summarizing file content until we can be more intelligent about what we send Co-Authored-By: Nathan Sobo --- crates/assistant/src/assistant_panel.rs | 10 ++++++---- crates/assistant/src/prompts.rs | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b69c12a2a3..62e2f61111 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -578,10 +578,7 @@ impl AssistantPanel { let codegen_kind = codegen.read(cx).kind().clone(); let user_prompt = user_prompt.to_string(); - let prompt = cx.background().spawn(async move { - let language_name = language_name.as_deref(); - generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) - }); + let mut messages = Vec::new(); let mut model = settings::get::(cx) .default_open_ai_model @@ -597,6 +594,11 @@ impl AssistantPanel { model = conversation.model.clone(); } + let prompt = cx.background().spawn(async move { + let language_name = language_name.as_deref(); + generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind) + }); + cx.spawn(|_, mut cx| async move { let prompt = prompt.await; diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index bf041dff52..7aca365776 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -121,6 +121,7 @@ pub fn generate_content_prompt( range: Range, kind: CodegenKind, ) -> String { + let range = range.to_offset(buffer); let mut prompt = String::new(); // General Preamble @@ -130,17 +131,29 @@ pub fn generate_content_prompt( writeln!(prompt, "You're an expert engineer.\n").unwrap(); } - let outline = summarize(buffer, range); + let mut content = String::new(); + content.extend(buffer.text_for_range(0..range.start)); + if range.start == range.end { + content.push_str("<|START|>"); + } else { + content.push_str("<|START|"); + } + content.extend(buffer.text_for_range(range.clone())); + if range.start != range.end { + content.push_str("|END|>"); + } + content.extend(buffer.text_for_range(range.end..buffer.len())); + writeln!( prompt, - "The file you are currently working on has the following outline:" + "The file you are currently working on has the following content:" ) .unwrap(); if let Some(language_name) = language_name { let language_name = language_name.to_lowercase(); - writeln!(prompt, "```{language_name}\n{outline}\n```").unwrap(); + writeln!(prompt, "```{language_name}\n{content}\n```").unwrap(); } else { - writeln!(prompt, "```\n{outline}\n```").unwrap(); + writeln!(prompt, "```\n{content}\n```").unwrap(); } match kind { From b366592878d01d3174fdbd2afb901bb354bea03b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 10 Oct 2023 19:00:05 +0200 Subject: [PATCH 064/141] Don't include start of a line when selection ends at start of line --- crates/assistant/src/assistant_panel.rs | 38 +++++++++++++++++-------- crates/assistant/src/codegen.rs | 23 ++------------- crates/assistant/src/prompts.rs | 1 + 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 62e2f61111..b1c6038602 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -17,7 +17,7 @@ use editor::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint, }, scroll::autoscroll::{Autoscroll, AutoscrollStrategy}, - Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, + Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, ToPoint, }; use fs::Fs; use futures::StreamExt; @@ -278,22 +278,36 @@ impl AssistantPanel { if selection.start.excerpt_id() != selection.end.excerpt_id() { return; } + let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); + + // Extend the selection to the start and the end of the line. + let mut point_selection = selection.map(|selection| selection.to_point(&snapshot)); + if point_selection.end > point_selection.start { + point_selection.start.column = 0; + // If the selection ends at the start of the line, we don't want to include it. + if point_selection.end.column == 0 { + point_selection.end.row -= 1; + } + point_selection.end.column = snapshot.line_len(point_selection.end.row); + } + + let codegen_kind = if point_selection.start == point_selection.end { + CodegenKind::Generate { + position: snapshot.anchor_after(point_selection.start), + } + } else { + CodegenKind::Transform { + range: snapshot.anchor_before(point_selection.start) + ..snapshot.anchor_after(point_selection.end), + } + }; let inline_assist_id = post_inc(&mut self.next_inline_assist_id); - let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); let provider = Arc::new(OpenAICompletionProvider::new( api_key, cx.background().clone(), )); - let codegen_kind = if editor.read(cx).selections.newest::(cx).is_empty() { - CodegenKind::Generate { - position: selection.start, - } - } else { - CodegenKind::Transform { - range: selection.start..selection.end, - } - }; + let codegen = cx.add_model(|cx| { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) }); @@ -319,7 +333,7 @@ impl AssistantPanel { editor.insert_blocks( [BlockProperties { style: BlockStyle::Flex, - position: selection.head().bias_left(&snapshot), + position: snapshot.anchor_before(point_selection.head()), height: 2, render: Arc::new({ let inline_assistant = inline_assistant.clone(); diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index e956d72260..b6ef6b5cfa 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -1,9 +1,7 @@ use crate::streaming_diff::{Hunk, StreamingDiff}; use ai::completion::{CompletionProvider, OpenAIRequest}; use anyhow::Result; -use editor::{ - multi_buffer, Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, -}; +use editor::{multi_buffer, Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; @@ -40,26 +38,11 @@ impl Entity for Codegen { impl Codegen { pub fn new( buffer: ModelHandle, - mut kind: CodegenKind, + kind: CodegenKind, provider: Arc, cx: &mut ModelContext, ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); - match &mut kind { - CodegenKind::Transform { range } => { - let mut point_range = range.to_point(&snapshot); - point_range.start.column = 0; - if point_range.end.column > 0 || point_range.start.row == point_range.end.row { - point_range.end.column = snapshot.line_len(point_range.end.row); - } - range.start = snapshot.anchor_before(point_range.start); - range.end = snapshot.anchor_after(point_range.end); - } - CodegenKind::Generate { position } => { - *position = position.bias_right(&snapshot); - } - } - Self { provider, buffer: buffer.clone(), @@ -386,7 +369,7 @@ mod tests { let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let range = buffer.read_with(cx, |buffer, cx| { let snapshot = buffer.snapshot(cx); - snapshot.anchor_before(Point::new(1, 4))..snapshot.anchor_after(Point::new(4, 4)) + snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) }); let provider = Arc::new(TestCompletionProvider::new()); let codegen = cx.add_model(|cx| { diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 7aca365776..d326a7f445 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -4,6 +4,7 @@ use std::cmp::{self, Reverse}; use std::fmt::Write; use std::ops::Range; +#[allow(dead_code)] fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> String { #[derive(Debug)] struct Match { From 40430cf01ba433a5f5a634b4779273f4c4f5c5da Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 10 Oct 2023 12:38:20 -0700 Subject: [PATCH 065/141] Update channel rooms to be ephemeral Remove redundant live kit initialization code Fix bug in recent channel links changes where channel rooms would have the incorrect release set co-authored-by: Conrad Irwin co-authored-by: Max --- .../20221109000000_test_schema.sql | 1 + ...0_add_unique_index_on_rooms_channel_id.sql | 1 + crates/collab/src/db/queries/channels.rs | 57 +++++++++-------- crates/collab/src/db/queries/rooms.rs | 5 +- crates/collab/src/db/tests/buffer_tests.rs | 6 +- crates/collab/src/db/tests/channel_tests.rs | 63 +++++++------------ crates/collab/src/db/tests/message_tests.rs | 20 ++---- crates/collab/src/rpc.rs | 17 ++--- crates/collab/src/tests/channel_tests.rs | 2 + .../src/tests/random_channel_buffer_tests.rs | 7 +-- crates/live_kit_client/src/test.rs | 5 +- 11 files changed, 73 insertions(+), 111 deletions(-) create mode 100644 crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index a9b8d9709d..e5104839b8 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -40,6 +40,7 @@ CREATE TABLE "rooms" ( "release_channel" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); +CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); CREATE TABLE "projects" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql b/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql new file mode 100644 index 0000000000..21ec4cfbb7 --- /dev/null +++ b/crates/collab/migrations/20231010114600_add_unique_index_on_rooms_channel_id.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index ab31f59541..b2fa2eb9b5 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -19,21 +19,14 @@ impl Database { .await } - pub async fn create_root_channel( - &self, - name: &str, - live_kit_room: &str, - creator_id: UserId, - ) -> Result { - self.create_channel(name, None, live_kit_room, creator_id) - .await + pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result { + self.create_channel(name, None, creator_id).await } pub async fn create_channel( &self, name: &str, parent: Option, - live_kit_room: &str, creator_id: UserId, ) -> Result { let name = Self::sanitize_channel_name(name)?; @@ -90,14 +83,6 @@ impl Database { .insert(&*tx) .await?; - room::ActiveModel { - channel_id: ActiveValue::Set(Some(channel.id)), - live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - ..Default::default() - } - .insert(&*tx) - .await?; - Ok(channel.id) }) .await @@ -797,18 +782,36 @@ impl Database { .await } - pub async fn room_id_for_channel(&self, channel_id: ChannelId) -> Result { + pub async fn get_or_create_channel_room( + &self, + channel_id: ChannelId, + live_kit_room: &str, + enviroment: &str, + ) -> Result { self.transaction(|tx| async move { let tx = tx; - let room = channel::Model { - id: channel_id, - ..Default::default() - } - .find_related(room::Entity) - .one(&*tx) - .await? - .ok_or_else(|| anyhow!("invalid channel"))?; - Ok(room.id) + + let room = room::Entity::find() + .filter(room::Column::ChannelId.eq(channel_id)) + .one(&*tx) + .await?; + + let room_id = if let Some(room) = room { + room.id + } else { + let result = room::Entity::insert(room::ActiveModel { + channel_id: ActiveValue::Set(Some(channel_id)), + live_kit_room: ActiveValue::Set(live_kit_room.to_string()), + release_channel: ActiveValue::Set(Some(enviroment.to_string())), + ..Default::default() + }) + .exec(&*tx) + .await?; + + result.last_insert_id + }; + + Ok(room_id) }) .await } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 6589f23791..4763b3f5cd 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -832,10 +832,7 @@ impl Database { let (channel_id, room) = self.get_channel_room(room_id, &tx).await?; let deleted = if room.participants.is_empty() { - let result = room::Entity::delete_by_id(room_id) - .filter(room::Column::ChannelId.is_null()) - .exec(&*tx) - .await?; + let result = room::Entity::delete_by_id(room_id).exec(&*tx).await?; result.rows_affected > 0 } else { false diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index f6e91b91f0..0ac41a8b0b 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -54,7 +54,7 @@ async fn test_channel_buffers(db: &Arc) { let owner_id = db.create_server("production").await.unwrap().0 as u32; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); db.invite_channel_member(zed_id, b_id, a_id, false) .await @@ -141,7 +141,7 @@ async fn test_channel_buffers(db: &Arc) { assert_eq!(left_buffer.connections, &[connection_id_a],); - let cargo_id = db.create_root_channel("cargo", "2", a_id).await.unwrap(); + let cargo_id = db.create_root_channel("cargo", a_id).await.unwrap(); let _ = db .join_channel_buffer(cargo_id, a_id, connection_id_a) .await @@ -207,7 +207,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { let mut text_buffers = Vec::new(); for i in 0..3 { let channel = db - .create_root_channel(&format!("channel-{i}"), &format!("room-{i}"), user_id) + .create_root_channel(&format!("channel-{i}"), user_id) .await .unwrap(); diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 2631e0d191..7d2bc04a35 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -45,7 +45,7 @@ async fn test_channels(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); // Make sure that people cannot read channels they haven't been invited to assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none()); @@ -58,16 +58,13 @@ async fn test_channels(db: &Arc) { .await .unwrap(); - let crdb_id = db - .create_channel("crdb", Some(zed_id), "2", a_id) - .await - .unwrap(); + let crdb_id = db.create_channel("crdb", Some(zed_id), a_id).await.unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(zed_id), "3", a_id) + .create_channel("livestreaming", Some(zed_id), a_id) .await .unwrap(); let replace_id = db - .create_channel("replace", Some(zed_id), "4", a_id) + .create_channel("replace", Some(zed_id), a_id) .await .unwrap(); @@ -75,14 +72,14 @@ async fn test_channels(db: &Arc) { members.sort(); assert_eq!(members, &[a_id, b_id]); - let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap(); + let rust_id = db.create_root_channel("rust", a_id).await.unwrap(); let cargo_id = db - .create_channel("cargo", Some(rust_id), "6", a_id) + .create_channel("cargo", Some(rust_id), a_id) .await .unwrap(); let cargo_ra_id = db - .create_channel("cargo-ra", Some(cargo_id), "7", a_id) + .create_channel("cargo-ra", Some(cargo_id), a_id) .await .unwrap(); @@ -202,11 +199,11 @@ async fn test_joining_channels(db: &Arc) { .unwrap() .user_id; - let channel_1 = db - .create_root_channel("channel_1", "1", user_1) + let channel_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); + let room_1 = db + .get_or_create_channel_room(channel_1, "1", TEST_RELEASE_CHANNEL) .await .unwrap(); - let room_1 = db.room_id_for_channel(channel_1).await.unwrap(); // can join a room with membership to its channel let joined_room = db @@ -283,15 +280,9 @@ async fn test_channel_invites(db: &Arc) { .unwrap() .user_id; - let channel_1_1 = db - .create_root_channel("channel_1", "1", user_1) - .await - .unwrap(); + let channel_1_1 = db.create_root_channel("channel_1", user_1).await.unwrap(); - let channel_1_2 = db - .create_root_channel("channel_2", "2", user_1) - .await - .unwrap(); + let channel_1_2 = db.create_root_channel("channel_2", user_1).await.unwrap(); db.invite_channel_member(channel_1_1, user_2, user_1, false) .await @@ -353,7 +344,7 @@ async fn test_channel_invites(db: &Arc) { .unwrap(); let channel_1_3 = db - .create_channel("channel_3", Some(channel_1_1), "1", user_1) + .create_channel("channel_3", Some(channel_1_1), user_1) .await .unwrap(); @@ -415,7 +406,7 @@ async fn test_channel_renames(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap(); + let zed_id = db.create_root_channel("zed", user_1).await.unwrap(); db.rename_channel(zed_id, user_1, "#zed-archive") .await @@ -460,25 +451,22 @@ async fn test_db_channel_moving(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", a_id).await.unwrap(); - let crdb_id = db - .create_channel("crdb", Some(zed_id), "2", a_id) - .await - .unwrap(); + let crdb_id = db.create_channel("crdb", Some(zed_id), a_id).await.unwrap(); let gpui2_id = db - .create_channel("gpui2", Some(zed_id), "3", a_id) + .create_channel("gpui2", Some(zed_id), a_id) .await .unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(crdb_id), "4", a_id) + .create_channel("livestreaming", Some(crdb_id), a_id) .await .unwrap(); let livestreaming_dag_id = db - .create_channel("livestreaming_dag", Some(livestreaming_id), "5", a_id) + .create_channel("livestreaming_dag", Some(livestreaming_id), a_id) .await .unwrap(); @@ -531,12 +519,7 @@ async fn test_db_channel_moving(db: &Arc) { // ======================================================================== // Create a new channel below a channel with multiple parents let livestreaming_dag_sub_id = db - .create_channel( - "livestreaming_dag_sub", - Some(livestreaming_dag_id), - "6", - a_id, - ) + .create_channel("livestreaming_dag_sub", Some(livestreaming_dag_id), a_id) .await .unwrap(); @@ -826,15 +809,15 @@ async fn test_db_channel_moving_bugs(db: &Arc) { .unwrap() .user_id; - let zed_id = db.create_root_channel("zed", "1", user_id).await.unwrap(); + let zed_id = db.create_root_channel("zed", user_id).await.unwrap(); let projects_id = db - .create_channel("projects", Some(zed_id), "2", user_id) + .create_channel("projects", Some(zed_id), user_id) .await .unwrap(); let livestreaming_id = db - .create_channel("livestreaming", Some(projects_id), "3", user_id) + .create_channel("livestreaming", Some(projects_id), user_id) .await .unwrap(); diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index 464aaba207..e758fcfb5d 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -25,10 +25,7 @@ async fn test_channel_message_retrieval(db: &Arc) { .await .unwrap() .user_id; - let channel = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; db.join_channel_chat(channel, rpc::ConnectionId { owner_id, id: 0 }, user) @@ -90,10 +87,7 @@ async fn test_channel_message_nonces(db: &Arc) { .await .unwrap() .user_id; - let channel = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel = db.create_channel("channel", None, user).await.unwrap(); let owner_id = db.create_server("test").await.unwrap().0 as u32; @@ -157,15 +151,9 @@ async fn test_channel_message_new_notification(db: &Arc) { .unwrap() .user_id; - let channel_1 = db - .create_channel("channel", None, "room", user) - .await - .unwrap(); + let channel_1 = db.create_channel("channel", None, user).await.unwrap(); - let channel_2 = db - .create_channel("channel-2", None, "room", user) - .await - .unwrap(); + let channel_2 = db.create_channel("channel-2", None, user).await.unwrap(); db.invite_channel_member(channel_1, observer, user, false) .await diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 268228077f..e5c6d94ce0 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -938,11 +938,6 @@ async fn create_room( util::async_iife!({ let live_kit = live_kit?; - live_kit - .create_room(live_kit_room.clone()) - .await - .trace_err()?; - let token = live_kit .room_token(&live_kit_room, &session.user_id.to_string()) .trace_err()?; @@ -2206,15 +2201,10 @@ async fn create_channel( session: Session, ) -> Result<()> { let db = session.db().await; - let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); - - if let Some(live_kit) = session.live_kit_client.as_ref() { - live_kit.create_room(live_kit_room.clone()).await?; - } let parent_id = request.parent_id.map(|id| ChannelId::from_proto(id)); let id = db - .create_channel(&request.name, parent_id, &live_kit_room, session.user_id) + .create_channel(&request.name, parent_id, session.user_id) .await?; let channel = proto::Channel { @@ -2619,12 +2609,15 @@ async fn join_channel( session: Session, ) -> Result<()> { let channel_id = ChannelId::from_proto(request.channel_id); + let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); let joined_room = { leave_room_for_session(&session).await?; let db = session.db().await; - let room_id = db.room_id_for_channel(channel_id).await?; + let room_id = db + .get_or_create_channel_room(channel_id, &live_kit_room, &*RELEASE_CHANNEL_NAME) + .await?; let joined_room = db .join_room( diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 6bdcee6af3..7cfcce832b 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -380,6 +380,8 @@ async fn test_channel_room( // Give everyone a chance to observe user A joining deterministic.run_until_parked(); + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + room_a.read_with(cx_a, |room, _| assert!(room.is_connected())); client_a.channel_store().read_with(cx_a, |channels, _| { assert_participants_eq( diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index ad0181602c..6e0bef225c 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -46,12 +46,7 @@ impl RandomizedTest for RandomChannelBufferTest { let db = &server.app_state.db; for ix in 0..CHANNEL_COUNT { let id = db - .create_channel( - &format!("channel-{ix}"), - None, - &format!("livekit-room-{ix}"), - users[0].user_id, - ) + .create_channel(&format!("channel-{ix}"), None, users[0].user_id) .await .unwrap(); for user in &users[1..] { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 704760bab7..8df8ab4abb 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -91,9 +91,8 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); - let room = server_rooms - .get_mut(&*room_name) - .ok_or_else(|| anyhow!("room {:?} does not exist", room_name))?; + let room = (*server_rooms).entry(room_name.to_string()).or_default(); + if room.client_rooms.contains_key(&identity) { Err(anyhow!( "{:?} attempted to join room {:?} twice", From e6228ca682156f28476338096aab3f5cb9e214d9 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 10 Oct 2023 16:04:31 -0400 Subject: [PATCH 066/141] Slim down pull request template --- .github/pull_request_template.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2cd2050013..147402b285 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,11 +2,4 @@ Release Notes: -- N/A - -or - - (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). - -If the release notes are only intended for a specific release channel only, add `(-only)` to the end of the release note line. -These will be removed by the person making the release. From d7d027bcf1cd8b87232c94654d1f6c885d84c814 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 10 Oct 2023 13:23:03 -0700 Subject: [PATCH 067/141] Rename release channel to enviroment --- .../20221109000000_test_schema.sql | 2 +- ...20231009181554_add_release_channel_to_rooms.sql | 2 +- crates/collab/src/db/queries/channels.rs | 2 +- crates/collab/src/db/queries/rooms.rs | 14 +++++++------- crates/collab/src/db/tables/room.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index e5104839b8..5a84bfd796 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,7 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, - "release_channel" VARCHAR, + "enviroment" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql index 95d3c400fc..8f3a704add 100644 --- a/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql +++ b/crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql @@ -1 +1 @@ -ALTER TABLE rooms ADD COLUMN release_channel TEXT; +ALTER TABLE rooms ADD COLUMN enviroment TEXT; diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b2fa2eb9b5..c576d2406b 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -802,7 +802,7 @@ impl Database { let result = room::Entity::insert(room::ActiveModel { channel_id: ActiveValue::Set(Some(channel_id)), live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - release_channel: ActiveValue::Set(Some(enviroment.to_string())), + enviroment: ActiveValue::Set(Some(enviroment.to_string())), ..Default::default() }) .exec(&*tx) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 4763b3f5cd..a38c77dc0f 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -112,7 +112,7 @@ impl Database { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), - release_channel: ActiveValue::set(Some(release_channel.to_string())), + enviroment: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -272,28 +272,28 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, - collab_release_channel: &str, + enviroment: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelIdAndReleaseChannel { + enum QueryChannelIdAndEnviroment { ChannelId, - ReleaseChannel, + Enviroment, } let (channel_id, release_channel): (Option, Option) = room::Entity::find() .select_only() .column(room::Column::ChannelId) - .column(room::Column::ReleaseChannel) + .column(room::Column::Enviroment) .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelIdAndReleaseChannel>() + .into_values::<_, QueryChannelIdAndEnviroment>() .one(&*tx) .await? .ok_or_else(|| anyhow!("no such room"))?; if let Some(release_channel) = release_channel { - if &release_channel != collab_release_channel { + if &release_channel != enviroment { Err(anyhow!("must join using the {} release", release_channel))?; } } diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index 7f31edcdbd..4150c741ac 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,7 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, - pub release_channel: Option, + pub enviroment: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] From 96d60eff237ffd357b74ee53a154dd4f6e151b77 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Oct 2023 15:40:40 -0700 Subject: [PATCH 068/141] Fix inclusion of spurious views from other projects in FollowResponse --- crates/workspace/src/workspace.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index c3c6f9a4b6..8b068fa10c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2878,8 +2878,7 @@ impl Workspace { let cx = &cx; move |item| { let item = item.to_followable_item_handle(cx)?; - if project_id.is_some() - && project_id != follower_project_id + if (project_id.is_none() || project_id != follower_project_id) && item.is_project_item(cx) { return None; From 1de9add304f2d2e787becd3fe49f656d9e45561b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 10 Oct 2023 18:46:49 -0600 Subject: [PATCH 069/141] vim: Add shift-y --- assets/keymaps/vim.json | 1 + crates/vim/src/normal.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 07ba8a121f..ea025747d8 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -408,6 +408,7 @@ "vim::PushOperator", "Yank" ], + "shift-y": "vim::YankLine", "i": "vim::InsertBefore", "shift-i": "vim::InsertFirstNonWhitespace", "a": "vim::InsertAfter", diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 36eab2c4c0..c8b517edd0 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -46,6 +46,7 @@ actions!( ChangeToEndOfLine, DeleteToEndOfLine, Yank, + YankLine, ChangeCase, JoinLines, ] @@ -66,6 +67,7 @@ pub fn init(cx: &mut AppContext) { cx.add_action(insert_line_above); cx.add_action(insert_line_below); cx.add_action(change_case); + cx.add_action(yank_line); cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| { Vim::update(cx, |vim, cx| { @@ -308,6 +310,13 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex }); } +fn yank_line(_: &mut Workspace, _: &YankLine, cx: &mut ViewContext) { + Vim::update(cx, |vim, cx| { + let count = vim.take_count(cx); + yank_motion(vim, motion::Motion::CurrentLine, count, cx) + }) +} + pub(crate) fn normal_replace(text: Arc, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { vim.stop_recording(); From 821997d3721911cdad9b2ed6c8537f38adc0e347 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 10 Oct 2023 19:59:57 -0600 Subject: [PATCH 070/141] Revert accidental build change --- .cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e22bdb0f2c..9da6b3be08 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ xtask = "run --package xtask --" [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0", "-C", "link-arg=-fuse-ld=/opt/homebrew/Cellar/llvm/16.0.6/bin/ld64.lld"] +rustflags = ["-C", "symbol-mangling-version=v0"] From 76191fe47de407754c093053a5c39cda89f1590c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 01:54:32 -0400 Subject: [PATCH 071/141] Fix Discord text truncation --- .github/workflows/release_actions.yml | 36 +++++++++++---------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 54e5d07787..c1df24a8e5 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -6,34 +6,28 @@ jobs: discord_release: runs-on: ubuntu-latest steps: - - name: Get appropriate URL - id: get-appropriate-url + - name: Get release URL + id: get-release-url run: | if [ "${{ github.event.release.prerelease }}" == "true" ]; then - url="https://zed.dev/releases/preview/latest" + URL="https://zed.dev/releases/preview/latest" else - url="https://zed.dev/releases/stable/latest" + URL="https://zed.dev/releases/stable/latest" fi - echo "::set-output name=url::$url" + echo "::set-output name=URL::$URL" + - name: Get content + uses: 2428392/gh-truncate-string-action@v1.2.0 + id: get-content + with: + stringToTruncate: | + 📣 Zed ${{ github.event.release.tag_name }} was just released! - - name: Prepare release content - id: prepare-content - run: | - set -eu - - text="📣 Zed ${{ github.event.release.tag_name }} was just released!\n\nRestart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.\n\n${{ github.event.release.body }}" - - maxTextLength=2000 - truncationIndicator="..." - - if (( ${#text} > maxTextLength )); then - text=${text:0:maxTextLength - ${#truncationIndicator}}$truncationIndicator - fi - - echo "::set-output name=content::$text" + Restart your Zed or head to ${{ steps.get-release-url.outputs.URL }} to grab it. + ${{ github.event.release.body }} + maxLength: 2000 - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} - content: ${{ steps.prepare-content.outputs.content }} + content: ${{ steps.get-content.outputs.string }} From eced842dfc281dc24dcb8b3cd9ed4511d7102376 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 22 Aug 2023 16:12:59 -0400 Subject: [PATCH 072/141] Get started with a prettier server package Co-Authored-By: Antonio Scandurra --- Cargo.lock | 4 ++ Cargo.toml | 1 + crates/prettier/Cargo.toml | 9 +++ .../prettier_server/.zed/settings.json | 7 +++ crates/prettier/prettier_server/package.json | 11 ++++ crates/prettier/prettier_server/src/index.js | 56 +++++++++++++++++++ crates/prettier/src/prettier.rs | 14 +++++ 7 files changed, 102 insertions(+) create mode 100644 crates/prettier/Cargo.toml create mode 100644 crates/prettier/prettier_server/.zed/settings.json create mode 100644 crates/prettier/prettier_server/package.json create mode 100644 crates/prettier/prettier_server/src/index.js create mode 100644 crates/prettier/src/prettier.rs diff --git a/Cargo.lock b/Cargo.lock index cd36221de0..8b1278b3dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5517,6 +5517,10 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettier" +version = "0.1.0" + [[package]] name = "pretty_assertions" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 7dae3bd81f..25aec39cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "crates/plugin", "crates/plugin_macros", "crates/plugin_runtime", + "crates/prettier", "crates/project", "crates/project_panel", "crates/project_symbols", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml new file mode 100644 index 0000000000..798899df0e --- /dev/null +++ b/crates/prettier/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "prettier" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[lib] +path = "src/prettier.rs" diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json new file mode 100644 index 0000000000..856536c62d --- /dev/null +++ b/crates/prettier/prettier_server/.zed/settings.json @@ -0,0 +1,7 @@ +{ + "languages": { + "JavaScript": { + "tab_size": 4 + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json new file mode 100644 index 0000000000..70c834b37e --- /dev/null +++ b/crates/prettier/prettier_server/package.json @@ -0,0 +1,11 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries" +} diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js new file mode 100644 index 0000000000..c8f2f3bb37 --- /dev/null +++ b/crates/prettier/prettier_server/src/index.js @@ -0,0 +1,56 @@ +const { Buffer } = require('buffer'); + +let buffer = Buffer.alloc(0); +process.stdin.resume(); +process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + handleData(); +}); +process.stdin.on('end', () => { + handleData(); +}); + +function handleData() { + if (buffer.length < 4) { + return; + } + + const length = buffer.readUInt32LE(0); + console.log(length); + if (buffer.length < 4 + length) { + return; + } + + const bytes = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + + try { + const message = JSON.parse(bytes); + handleMessage(message); + } catch (_) { + sendResponse(makeError("Request JSON parse error")); + return; + } +} + +// format +// clear_cache +// shutdown +// error + +function handleMessage(message) { + console.log(message); + sendResponse({ method: "hi", result: null }); +} + +function makeError(message) { + return { method: "error", message }; +} + +function sendResponse(response) { + let message = Buffer.from(JSON.stringify(response)); + let length = Buffer.alloc(4); + length.writeUInt32LE(message.length); + process.stdout.write(length); + process.stdout.write(message); +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs new file mode 100644 index 0000000000..7d12d9af81 --- /dev/null +++ b/crates/prettier/src/prettier.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 553abd01beded42f9fdb64feecf8dcff30eac5cb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 1 Sep 2023 18:31:16 +0300 Subject: [PATCH 073/141] Draft a project part of the prettier --- Cargo.lock | 7 +++ crates/language/src/language_settings.rs | 7 ++- crates/prettier/Cargo.toml | 15 ++++++- crates/prettier/src/prettier.rs | 50 +++++++++++++++++---- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 56 +++++++++++++++++++++++- 6 files changed, 122 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b1278b3dc..120669f037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettier" version = "0.1.0" +dependencies = [ + "anyhow", + "fs", + "gpui", + "language", +] [[package]] name = "pretty_assertions" @@ -5635,6 +5641,7 @@ dependencies = [ "lsp", "parking_lot 0.11.2", "postage", + "prettier", "pretty_assertions", "rand 0.8.5", "regex", diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index c3f706802a..8778ba8d64 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -149,10 +149,15 @@ pub enum ShowWhitespaceSetting { All, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { + #[default] + Auto, LanguageServer, + Prettier { + config: (), // Support some of the most important settings in the prettier-vscode extension. + }, External { command: Arc, arguments: Arc<[String]>, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 798899df0e..c2e4b2a3cc 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -3,7 +3,18 @@ name = "prettier" version = "0.1.0" edition = "2021" -[dependencies] - [lib] path = "src/prettier.rs" + +[dependencies] +language = { path = "../language" } +gpui = { path = "../gpui" } +fs = { path = "../fs" } + +anyhow.workspace = true + + +[dev-dependencies] +language = { path = "../language", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7d12d9af81..9b1b9c1e9d 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,14 +1,46 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +pub use std::path::{Path, PathBuf}; +pub use std::sync::Arc; + +use fs::Fs; +use gpui::ModelHandle; +use language::{Buffer, Diff}; + +pub struct Prettier { + _private: (), } -#[cfg(test)] -mod tests { - use super::*; +type NodeRuntime = (); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +impl Prettier { + // This was taken from the prettier-vscode extension. + pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ + ".prettierrc", + ".prettierrc.json", + ".prettierrc.json5", + ".prettierrc.yaml", + ".prettierrc.yml", + ".prettierrc.toml", + ".prettierrc.js", + ".prettierrc.cjs", + "package.json", + "prettier.config.js", + "prettier.config.cjs", + ".editorconfig", + ]; + + pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { + todo!() + } + + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + todo!() + } + + pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { + todo!() + } + + pub async fn clear_cache(&self) -> anyhow::Result<()> { + todo!() } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index ffea6646e9..c22193f130 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } sum_tree = { path = "../sum_tree" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a50e02a631..42b0a13c51 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,6 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; +use prettier::Prettier; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -152,6 +153,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, } struct DelayedDebounced { @@ -660,6 +662,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), } }) } @@ -757,6 +760,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + prettier_instances: HashMap::default(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -4027,6 +4031,7 @@ impl Project { enum FormatOperation { Lsp(Vec<(Range, String)>), External(Diff), + Prettier(Diff), } // Apply language-specific formatting using either a language server @@ -4062,8 +4067,8 @@ impl Project { | (_, FormatOnSave::External { command, arguments }) => { if let Some(buffer_abs_path) = buffer_abs_path { format_operation = Self::format_via_external_command( - &buffer, - &buffer_abs_path, + buffer, + buffer_abs_path, &command, &arguments, &mut cx, @@ -4076,6 +4081,45 @@ impl Project { .map(FormatOperation::External); } } + (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("autoformatting via prettier")?, + )); + } else if let Some((language_server, buffer_abs_path)) = + language_server.as_ref().zip(buffer_abs_path.as_ref()) + { + format_operation = Some(FormatOperation::Lsp( + Self::format_via_lsp( + &this, + &buffer, + buffer_abs_path, + &language_server, + tab_size, + &mut cx, + ) + .await + .context("failed to format via language server")?, + )); + } + } + (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { + if let Some(prettier) = this.update(&mut cx, |project, _| { + project.prettier_instance_for_buffer(buffer) + }) { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + } }; buffer.update(&mut cx, |b, cx| { @@ -4100,6 +4144,9 @@ impl Project { FormatOperation::External(diff) => { b.apply_diff(diff, cx); } + FormatOperation::Prettier(diff) => { + b.apply_diff(diff, cx); + } } if let Some(transaction_id) = whitespace_transaction_id { @@ -8109,6 +8156,11 @@ impl Project { Vec::new() } } + + fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { + // TODO kb + None + } } fn subscribe_for_copilot_events( From 92f23e626eba18fb4653a653cd600e11ada45c90 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 15:51:46 +0300 Subject: [PATCH 074/141] Properly connect prettier lookup/creation methods --- crates/prettier/src/prettier.rs | 2 +- crates/project/src/project.rs | 115 +++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9b1b9c1e9d..ecd4376477 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,7 +9,7 @@ pub struct Prettier { _private: (), } -type NodeRuntime = (); +pub struct NodeRuntime; impl Prettier { // This was taken from the prettier-vscode extension. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 42b0a13c51..d24fe17380 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::Prettier; +use prettier::{NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -153,7 +153,10 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - prettier_instances: HashMap<(WorktreeId, PathBuf), Shared>>>>, + prettier_instances: HashMap< + (Option, PathBuf), + Shared, Arc>>>, + >, } struct DelayedDebounced { @@ -3953,7 +3956,7 @@ impl Project { push_to_history: bool, trigger: FormatTrigger, cx: &mut ModelContext, - ) -> Task> { + ) -> Task> { if self.is_local() { let mut buffers_with_paths_and_servers = buffers .into_iter() @@ -4082,15 +4085,26 @@ impl Project { } } (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("autoformatting via prettier")?, - )); + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during autoformatting: {e:#}" + ), + } } else if let Some((language_server, buffer_abs_path)) = language_server.as_ref().zip(buffer_abs_path.as_ref()) { @@ -4109,16 +4123,27 @@ impl Project { } } (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => { - if let Some(prettier) = this.update(&mut cx, |project, _| { - project.prettier_instance_for_buffer(buffer) - }) { - format_operation = Some(FormatOperation::Prettier( - prettier - .format(buffer) + if let Some(prettier_task) = this + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(buffer, cx) + }) { + match prettier_task .await - .context("formatting via prettier")?, - )); - } + .await + { + Ok(prettier) => { + format_operation = Some(FormatOperation::Prettier( + prettier + .format(buffer) + .await + .context("formatting via prettier")?, + )); + } + Err(e) => anyhow::bail!( + "Failed to create prettier instance for buffer during formatting: {e:#}" + ), + } + } } }; @@ -8157,9 +8182,53 @@ impl Project { } } - fn prettier_instance_for_buffer(&self, buffer: &ModelHandle) -> Option { - // TODO kb - None + fn prettier_instance_for_buffer( + &mut self, + buffer: &ModelHandle, + cx: &mut ModelContext, + ) -> Option, Arc>>>>> { + let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); + + // TODO kb return None if config opted out of prettier + + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + // TODO kb can we have a cache for this instead? + let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_path.clone())) + .cloned() + }) { + return existing_prettier; + } + + let task_node_runtime = Arc::new(NodeRuntime); + let task_prettier_path = prettier_path.clone(); + let new_prettier_task = cx + .background() + .spawn(async move { + Ok(Arc::new( + Prettier::start(&task_prettier_path, task_node_runtime) + .await + .with_context(|| { + format!("starting new prettier for path {task_prettier_path:?}") + })?, + )) + .map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_path), new_prettier_task.clone()); + }); + new_prettier_task + }); + Some(task) } } From a8dfa013621833d342902ed887f0e5c49da19bde Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 5 Sep 2023 17:09:22 +0300 Subject: [PATCH 075/141] Prepare prettier file lookup code infra --- Cargo.lock | 1 + assets/settings/default.json | 3 +- crates/fs/src/fs.rs | 5 +- crates/prettier/Cargo.toml | 2 +- crates/prettier/src/prettier.rs | 111 +++++++++++++++++++++++++++++++- crates/project/src/project.rs | 51 ++++++++++++++- 6 files changed, 165 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 120669f037..a0daee9305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ version = "0.1.0" dependencies = [ "anyhow", "fs", + "futures 0.3.28", "gpui", "language", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index cc724657c0..be47ac9c8c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,8 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - "formatter": "language_server", + // TODO kb description + "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 1d95db9b6c..bb5d6387e0 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -85,7 +85,7 @@ pub struct RemoveOptions { pub ignore_if_not_exists: bool, } -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub struct Metadata { pub inode: u64, pub mtime: SystemTime, @@ -229,11 +229,12 @@ impl Fs for RealFs { } else { symlink_metadata }; + let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: metadata.file_type().is_dir(), + is_dir: file_type_metadata.is_dir(), })) } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index c2e4b2a3cc..821cde7b3a 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -12,7 +12,7 @@ gpui = { path = "../gpui" } fs = { path = "../fs" } anyhow.workspace = true - +futures.workspace = true [dev-dependencies] language = { path = "../language", features = ["test-support"] } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ecd4376477..a38f8f8651 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,6 +1,8 @@ +use std::collections::VecDeque; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; +use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; @@ -11,6 +13,12 @@ pub struct Prettier { pub struct NodeRuntime; +#[derive(Debug)] +pub struct LocateStart { + pub worktree_root_path: Arc, + pub starting_path: Arc, +} + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ @@ -28,8 +36,107 @@ impl Prettier { ".editorconfig", ]; - pub async fn locate(starting_path: Option<&Path>, fs: Arc) -> PathBuf { - todo!() + pub async fn locate( + starting_path: Option, + fs: Arc, + ) -> anyhow::Result { + let paths_to_check = match starting_path { + Some(starting_path) => { + let worktree_root = starting_path + .worktree_root_path + .components() + .into_iter() + .take_while(|path_component| { + path_component.as_os_str().to_str() != Some("node_modules") + }) + .collect::(); + + if worktree_root != starting_path.worktree_root_path.as_ref() { + vec![worktree_root] + } else { + let (worktree_root_metadata, start_path_metadata) = if starting_path + .starting_path + .as_ref() + == Path::new("") + { + let worktree_root_data = + fs.metadata(&worktree_root).await.with_context(|| { + format!( + "FS metadata fetch for worktree root path {worktree_root:?}", + ) + })?; + (worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), None) + } else { + let full_starting_path = worktree_root.join(&starting_path.starting_path); + let (worktree_root_data, start_path_data) = futures::try_join!( + fs.metadata(&worktree_root), + fs.metadata(&full_starting_path), + ) + .with_context(|| { + format!("FS metadata fetch for starting path {full_starting_path:?}",) + })?; + ( + worktree_root_data.unwrap_or_else(|| { + panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}") + }), + start_path_data, + ) + }; + + match start_path_metadata { + Some(start_path_metadata) => { + anyhow::ensure!(worktree_root_metadata.is_dir, + "For non-empty start path, worktree root {starting_path:?} should be a directory"); + anyhow::ensure!( + !start_path_metadata.is_dir, + "For non-empty start path, it should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !start_path_metadata.is_symlink, + "For non-empty start path, it should not be a symlink {starting_path:?}" + ); + + let file_to_format = starting_path.starting_path.as_ref(); + let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]); + let mut current_path = worktree_root; + for path_component in file_to_format.components().into_iter() { + current_path = current_path.join(path_component); + paths_to_check.push_front(current_path.clone()); + if path_component.as_os_str().to_str() == Some("node_modules") { + break; + } + } + paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it + Vec::from(paths_to_check) + } + None => { + anyhow::ensure!( + !worktree_root_metadata.is_dir, + "For empty start path, worktree root should not be a directory {starting_path:?}" + ); + anyhow::ensure!( + !worktree_root_metadata.is_symlink, + "For empty start path, worktree root should not be a symlink {starting_path:?}" + ); + worktree_root + .parent() + .map(|path| vec![path.to_path_buf()]) + .unwrap_or_default() + } + } + } + } + None => Vec::new(), + }; + + if dbg!(paths_to_check).is_empty() { + // TODO kb return the default prettier, how, without state? + } else { + // TODO kb now check all paths to check for prettier + } + Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d24fe17380..6538a26540 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use prettier::{NodeRuntime, Prettier}; +use prettier::{LocateStart, NodeRuntime, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -8189,14 +8189,61 @@ impl Project { ) -> Option, Arc>>>>> { let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); + let worktree_path = buffer_file + .as_ref() + .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); // TODO kb return None if config opted out of prettier + if true { + let fs = Arc::clone(&self.fs); + let buffer_path = buffer_path.clone(); + let worktree_path = worktree_path.clone(); + cx.spawn(|_, _| async move { + let prettier_path = Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| { + dbg!(LocateStart { + worktree_root_path, + starting_path, + }) + }), + fs, + ) + .await + .unwrap(); + dbg!(prettier_path); + }) + .detach(); + return None; + } let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = Prettier::locate(buffer_path.as_deref(), fs).await; + let prettier_path = match cx + .background() + .spawn(Prettier::locate( + worktree_path + .zip(buffer_path) + .map(|(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }), + fs, + )) + .await + { + Ok(path) => path, + Err(e) => { + return Task::Ready(Some(Result::Err(Arc::new( + e.context("determining prettier path for worktree {worktree_path:?}"), + )))) + .shared(); + } + }; + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances From a420d9cdc73738a38e380671364d05f1c829bb12 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 13:57:50 +0300 Subject: [PATCH 076/141] Add prettier search --- Cargo.lock | 3 ++ crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 67 +++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0daee9305..9761e040d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,9 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "serde", + "serde_derive", + "serde_json", ] [[package]] diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 821cde7b3a..77a845b5b3 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,9 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true anyhow.workspace = true futures.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a38f8f8651..ca53bb7a03 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; pub use std::path::{Path, PathBuf}; pub use std::sync::Arc; @@ -40,7 +40,7 @@ impl Prettier { starting_path: Option, fs: Arc, ) -> anyhow::Result { - let paths_to_check = match starting_path { + let paths_to_check = match starting_path.as_ref() { Some(starting_path) => { let worktree_root = starting_path .worktree_root_path @@ -131,12 +131,16 @@ impl Prettier { None => Vec::new(), }; - if dbg!(paths_to_check).is_empty() { - // TODO kb return the default prettier, how, without state? - } else { - // TODO kb now check all paths to check for prettier + match find_closest_prettier_path(paths_to_check, fs.as_ref()) + .await + .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + { + Some(prettier_path) => Ok(prettier_path), + None => { + // TODO kb return the default prettier, how, without state? + Ok(PathBuf::new()) + } } - Ok(PathBuf::new()) } pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { @@ -151,3 +155,52 @@ impl Prettier { todo!() } } + +const PRETTIER_PACKAGE_NAME: &str = "prettier"; +async fn find_closest_prettier_path( + paths_to_check: Vec, + fs: &dyn Fs, +) -> anyhow::Result> { + for path in paths_to_check { + let possible_package_json = path.join("package.json"); + if let Some(package_json_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? + { + if !package_json_metadata.is_dir && !package_json_metadata.is_symlink { + let package_json_contents = fs + .load(&possible_package_json) + .await + .with_context(|| format!("reading {possible_package_json:?} file contents"))?; + if let Ok(json_contents) = serde_json::from_str::>( + &package_json_contents, + ) { + if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies") + { + if o.contains_key(PRETTIER_PACKAGE_NAME) { + return Ok(Some(path)); + } + } + } + } + } + + let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); + if let Some(node_modules_location_metadata) = fs + .metadata(&path) + .await + .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? + { + if node_modules_location_metadata.is_dir { + return Ok(Some(path)); + } + } + } + Ok(None) +} From a8387b8b19e76c132e829277badeafbbc0220e22 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 17:23:04 +0300 Subject: [PATCH 077/141] Use proper NodeRuntime in the formatter interface --- Cargo.lock | 2 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/prettier_server/.gitignore | 1 + .../prettier_server/package-lock.json | 29 +++++++++++++++++++ crates/prettier/prettier_server/package.json | 21 ++++++++------ crates/prettier/prettier_server/src/index.js | 1 + crates/prettier/src/prettier.rs | 9 +++--- crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 24 +++++++++++---- crates/zed/src/main.rs | 2 +- 10 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 crates/prettier/prettier_server/.gitignore create mode 100644 crates/prettier/prettier_server/package-lock.json diff --git a/Cargo.lock b/Cargo.lock index 9761e040d3..de339e1546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "node_runtime", "serde", "serde_derive", "serde_json", @@ -5643,6 +5644,7 @@ dependencies = [ "lazy_static", "log", "lsp", + "node_runtime", "parking_lot 0.11.2", "postage", "prettier", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 77a845b5b3..2b8bf99fc2 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +node_runtime = { path = "../node_runtime"} serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore new file mode 100644 index 0000000000..c2658d7d1b --- /dev/null +++ b/crates/prettier/prettier_server/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json new file mode 100644 index 0000000000..6d495d72b4 --- /dev/null +++ b/crates/prettier/prettier_server/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0", + "dependencies": { + "prettier": "^3.0.3" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 70c834b37e..599f308e9f 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -1,11 +1,14 @@ { - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries" + "name": "prettier_server", + "version": "1.0.0", + "description": "", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Zed Industries", + "dependencies": { + "prettier": "^3.0" + } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index c8f2f3bb37..5ac35d7ef9 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -17,6 +17,7 @@ function handleData() { const length = buffer.readUInt32LE(0); console.log(length); + console.log(buffer.toString()); if (buffer.length < 4 + length) { return; } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index ca53bb7a03..efb3a0fcf5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,18 +1,17 @@ use std::collections::{HashMap, VecDeque}; -pub use std::path::{Path, PathBuf}; -pub use std::sync::Arc; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::ModelHandle; use language::{Buffer, Diff}; +use node_runtime::NodeRuntime; pub struct Prettier { _private: (), } -pub struct NodeRuntime; - #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -143,7 +142,7 @@ impl Prettier { } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { + pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { todo!() } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index c22193f130..9f505c3fd2 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -31,6 +31,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +node_runtime = { path = "../node_runtime" } prettier = { path = "../prettier" } rpc = { path = "../rpc" } settings = { path = "../settings" } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 6538a26540..b7fc1b8b34 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -49,8 +49,9 @@ use lsp::{ DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; +use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, NodeRuntime, Prettier}; +use prettier::{LocateStart, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -71,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, + Arc, OnceLock, }, time::{Duration, Instant}, }; @@ -553,14 +554,27 @@ impl SearchMatchCandidate { } } +static NODE_RUNTIME: OnceLock> = OnceLock::new(); + impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init(client: &Arc, cx: &mut AppContext) { + pub fn init( + client: &Arc, + node_runtime: Option>, + cx: &mut AppContext, + ) { Self::init_settings(cx); + // TODO kb move it to Project::local and other constructors? + if let Some(node_runtime) = node_runtime { + NODE_RUNTIME + .set(node_runtime) + .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); + } + client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -8187,6 +8201,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { + let node_runtime = Arc::clone(NODE_RUNTIME.get()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file @@ -8253,13 +8268,12 @@ impl Project { return existing_prettier; } - let task_node_runtime = Arc::new(NodeRuntime); let task_prettier_path = prettier_path.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, task_node_runtime) + Prettier::start(&task_prettier_path, node_runtime) .await .with_context(|| { format!("starting new prettier for path {task_prettier_path:?}") diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f89a880c71..8093632d3a 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, cx); + project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); From ce6b31d93897eb8c6059eba11ee5db0c4382c2c5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 6 Sep 2023 18:49:56 +0300 Subject: [PATCH 078/141] Make NodeRuntime non-static for prettier runner --- Cargo.lock | 2 ++ crates/collab/Cargo.toml | 1 + crates/project/src/project.rs | 37 ++++++++++++++++--------------- crates/workspace/Cargo.toml | 1 + crates/workspace/src/workspace.rs | 8 +++++++ crates/zed/src/main.rs | 10 +++++++-- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de339e1546..24e312803b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,6 +1501,7 @@ dependencies = [ "log", "lsp", "nanoid", + "node_runtime", "parking_lot 0.11.2", "pretty_assertions", "project", @@ -10003,6 +10004,7 @@ dependencies = [ "lazy_static", "log", "menu", + "node_runtime", "parking_lot 0.11.2", "postage", "project", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 6177c23620..cb22147b04 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -72,6 +72,7 @@ fs = { path = "../fs", features = ["test-support"] } git = { path = "../git", features = ["test-support"] } live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } +node_runtime = { path = "../node_runtime" } project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b7fc1b8b34..8f56a8be09 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -72,7 +72,7 @@ use std::{ str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, OnceLock, + Arc, }, time::{Duration, Instant}, }; @@ -154,6 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, + node_runtime: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -554,27 +555,14 @@ impl SearchMatchCandidate { } } -static NODE_RUNTIME: OnceLock> = OnceLock::new(); - impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); } - pub fn init( - client: &Arc, - node_runtime: Option>, - cx: &mut AppContext, - ) { + pub fn init(client: &Arc, cx: &mut AppContext) { Self::init_settings(cx); - // TODO kb move it to Project::local and other constructors? - if let Some(node_runtime) = node_runtime { - NODE_RUNTIME - .set(node_runtime) - .unwrap_or_else(|_| panic!("multiple init calls tried to set node runtime")); - } - client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -624,6 +612,7 @@ impl Project { pub fn local( client: Arc, + node_runtime: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -679,6 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: Some(node_runtime), prettier_instances: HashMap::default(), } }) @@ -777,6 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), + node_runtime: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -811,13 +802,23 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { + use node_runtime::FakeNodeRuntime; + let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let project = - cx.update(|cx| Project::local(client, user_store, Arc::new(languages), fs, cx)); + let project = cx.update(|cx| { + Project::local( + client, + FakeNodeRuntime::new(), + user_store, + Arc::new(languages), + fs, + cx, + ) + }); for path in root_paths { let (tree, _) = project .update(cx, |project, cx| { @@ -8201,7 +8202,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(NODE_RUNTIME.get()?); + let node_runtime = Arc::clone(self.node_runtime.as_ref()?); let buffer_file = File::from_dyn(buffer.read(cx).file()); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d1240a45ce..99f19ed9d0 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -30,6 +30,7 @@ gpui = { path = "../gpui" } install_cli = { path = "../install_cli" } language = { path = "../language" } menu = { path = "../menu" } +node_runtime = { path = "../node_runtime" } project = { path = "../project" } settings = { path = "../settings" } terminal = { path = "../terminal" } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8b068fa10c..454b0138e6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -42,6 +42,7 @@ use gpui::{ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use itertools::Itertools; use language::{LanguageRegistry, Rope}; +use node_runtime::NodeRuntime; use std::{ any::TypeId, borrow::Cow, @@ -456,6 +457,7 @@ pub struct AppState { pub initialize_workspace: fn(WeakViewHandle, bool, Arc, AsyncAppContext) -> Task>, pub background_actions: BackgroundActions, + pub node_runtime: Arc, } pub struct WorkspaceStore { @@ -474,6 +476,7 @@ struct Follower { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { + use node_runtime::FakeNodeRuntime; use settings::SettingsStore; if !cx.has_global::() { @@ -498,6 +501,7 @@ impl AppState { user_store, // channel_store, workspace_store, + node_runtime: FakeNodeRuntime::new(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), build_window_options: |_, _, _| Default::default(), background_actions: || &[], @@ -816,6 +820,7 @@ impl Workspace { )> { let project_handle = Project::local( app_state.client.clone(), + app_state.node_runtime.clone(), app_state.user_store.clone(), app_state.languages.clone(), app_state.fs.clone(), @@ -3517,6 +3522,8 @@ impl Workspace { #[cfg(any(test, feature = "test-support"))] pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { + use node_runtime::FakeNodeRuntime; + let client = project.read(cx).client(); let user_store = project.read(cx).user_store(); @@ -3530,6 +3537,7 @@ impl Workspace { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); Self::new(0, project, app_state, cx) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8093632d3a..16189f6c4e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -138,7 +138,7 @@ fn main() { theme::init(Assets, cx); context_menu::init(cx); - project::Project::init(&client, Some(Arc::clone(&node_runtime)), cx); + project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); language::init(cx); @@ -154,7 +154,12 @@ fn main() { semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx); vim::init(cx); terminal_view::init(cx); - copilot::init(copilot_language_server_id, http.clone(), node_runtime, cx); + copilot::init( + copilot_language_server_id, + http.clone(), + node_runtime.clone(), + cx, + ); assistant::init(cx); component_test::init(cx); @@ -181,6 +186,7 @@ fn main() { initialize_workspace, background_actions, workspace_store, + node_runtime, }); cx.set_global(Arc::downgrade(&app_state)); From 4f956d71e2de17dcce6588fef6696570a4918e27 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 15:41:23 +0300 Subject: [PATCH 079/141] Slightly better prettier settings and discovery --- Cargo.lock | 1 + assets/settings/default.json | 2 + crates/collab/src/tests/test_server.rs | 3 ++ crates/language/src/language_settings.rs | 4 ++ crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 32 ++++++++----- crates/project/src/project.rs | 60 ++++++++---------------- crates/util/src/paths.rs | 1 + 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24e312803b..0d7e250377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5531,6 +5531,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "util", ] [[package]] diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c..677cc57820 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,6 +201,8 @@ // } // TODO kb description "formatter": "auto", + // TODO kb description + better settings + "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2e13874125..ccd48a0a1b 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -15,6 +15,7 @@ use fs::FakeFs; use futures::{channel::oneshot, StreamExt as _}; use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHandle}; use language::LanguageRegistry; +use node_runtime::FakeNodeRuntime; use parking_lot::Mutex; use project::{Project, WorktreeId}; use rpc::RECEIVE_TIMEOUT; @@ -218,6 +219,7 @@ impl TestServer { build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _, _| Task::ready(Ok(())), background_actions: || &[], + node_runtime: FakeNodeRuntime::new(), }); cx.update(|cx| { @@ -569,6 +571,7 @@ impl TestClient { self.client().clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), + self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d64..844e0c7c36 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,6 +47,7 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, + pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -92,6 +93,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] + pub prettier: Option, + #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -398,6 +401,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); + merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 2b8bf99fc2..ab8d4d9e16 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -11,6 +11,7 @@ language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } node_runtime = { path = "../node_runtime"} +util = { path = "../util" } serde.workspace = true serde_derive.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index efb3a0fcf5..454f845aa4 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -130,20 +130,21 @@ impl Prettier { None => Vec::new(), }; - match find_closest_prettier_path(paths_to_check, fs.as_ref()) + match find_closest_prettier_dir(paths_to_check, fs.as_ref()) .await - .with_context(|| format!("Finding prettier starting with {starting_path:?}"))? + .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { - Some(prettier_path) => Ok(prettier_path), - None => { - // TODO kb return the default prettier, how, without state? - Ok(PathBuf::new()) - } + Some(prettier_dir) => Ok(prettier_dir), + None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), } } - pub async fn start(prettier_path: &Path, node: Arc) -> anyhow::Result { - todo!() + pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") } pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { @@ -156,14 +157,14 @@ impl Prettier { } const PRETTIER_PACKAGE_NAME: &str = "prettier"; -async fn find_closest_prettier_path( +async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, ) -> anyhow::Result> { for path in paths_to_check { let possible_package_json = path.join("package.json"); if let Some(package_json_metadata) = fs - .metadata(&path) + .metadata(&possible_package_json) .await .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))? { @@ -192,7 +193,7 @@ async fn find_closest_prettier_path( let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME); if let Some(node_modules_location_metadata) = fs - .metadata(&path) + .metadata(&possible_node_modules_location) .await .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))? { @@ -203,3 +204,10 @@ async fn find_closest_prettier_path( } Ok(None) } + +async fn prepare_default_prettier( + fs: Arc, + node: Arc, +) -> anyhow::Result { + todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f56a8be09..56b86e5274 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -154,7 +154,7 @@ pub struct Project { copilot_lsp_subscription: Option, copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, - node_runtime: Option>, + node: Option>, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, @@ -612,7 +612,7 @@ impl Project { pub fn local( client: Arc, - node_runtime: Arc, + node: Arc, user_store: ModelHandle, languages: Arc, fs: Arc, @@ -668,7 +668,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: Some(node_runtime), + node: Some(node), prettier_instances: HashMap::default(), } }) @@ -767,7 +767,7 @@ impl Project { copilot_lsp_subscription, copilot_log_subscription: None, current_lsp_settings: settings::get::(cx).lsp.clone(), - node_runtime: None, + node: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -802,8 +802,6 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { - use node_runtime::FakeNodeRuntime; - let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); @@ -812,7 +810,7 @@ impl Project { let project = cx.update(|cx| { Project::local( client, - FakeNodeRuntime::new(), + node_runtime::FakeNodeRuntime::new(), user_store, Arc::new(languages), fs, @@ -8202,43 +8200,25 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Option, Arc>>>>> { - let node_runtime = Arc::clone(self.node_runtime.as_ref()?); - let buffer_file = File::from_dyn(buffer.read(cx).file()); + let buffer = buffer.read(cx); + let buffer_file = buffer.file(); + let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); + if !language_settings.prettier { + return None; + } + + let node = Arc::clone(self.node.as_ref()?); + let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() .map(|file| file.worktree.read(cx).abs_path()); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - // TODO kb return None if config opted out of prettier - if true { - let fs = Arc::clone(&self.fs); - let buffer_path = buffer_path.clone(); - let worktree_path = worktree_path.clone(); - cx.spawn(|_, _| async move { - let prettier_path = Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| { - dbg!(LocateStart { - worktree_root_path, - starting_path, - }) - }), - fs, - ) - .await - .unwrap(); - dbg!(prettier_path); - }) - .detach(); - return None; - } - let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); // TODO kb can we have a cache for this instead? - let prettier_path = match cx + let prettier_dir = match cx .background() .spawn(Prettier::locate( worktree_path @@ -8263,21 +8243,21 @@ impl Project { if let Some(existing_prettier) = this.update(&mut cx, |project, _| { project .prettier_instances - .get(&(worktree_id, prettier_path.clone())) + .get(&(worktree_id, prettier_dir.clone())) .cloned() }) { return existing_prettier; } - let task_prettier_path = prettier_path.clone(); + let task_prettier_dir = prettier_dir.clone(); let new_prettier_task = cx .background() .spawn(async move { Ok(Arc::new( - Prettier::start(&task_prettier_path, node_runtime) + Prettier::start(&task_prettier_dir, node) .await .with_context(|| { - format!("starting new prettier for path {task_prettier_path:?}") + format!("starting new prettier for path {task_prettier_dir:?}") })?, )) .map_err(Arc::new) @@ -8286,7 +8266,7 @@ impl Project { this.update(&mut cx, |project, _| { project .prettier_instances - .insert((worktree_id, prettier_path), new_prettier_task.clone()); + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); new_prettier_task }); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 4578ce0bc9..96d77236a9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -11,6 +11,7 @@ lazy_static::lazy_static! { pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); + pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); From 12ea12e4e78d0ae2fcf53d110757f86cb48745e0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 8 Sep 2023 21:46:28 +0300 Subject: [PATCH 080/141] Make language adapters able to require certain bundled formatters --- assets/settings/default.json | 2 -- crates/language/src/language.rs | 24 ++++++++++++++++++++++++ crates/language/src/language_settings.rs | 4 ---- crates/project/src/project.rs | 21 ++++++++++++++------- crates/zed/src/languages/css.rs | 6 +++++- crates/zed/src/languages/html.rs | 6 +++++- crates/zed/src/languages/json.rs | 8 +++++++- crates/zed/src/languages/php.rs | 6 +++++- crates/zed/src/languages/svelte.rs | 6 +++++- crates/zed/src/languages/typescript.rs | 10 +++++++++- crates/zed/src/languages/yaml.rs | 7 ++++++- 11 files changed, 80 insertions(+), 20 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 677cc57820..be47ac9c8c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -201,8 +201,6 @@ // } // TODO kb description "formatter": "auto", - // TODO kb description + better settings - "prettier": true, // How to soft-wrap long lines of text. This setting can take // three values: // diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7d113a88af..ed0a0d8ee8 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -227,6 +227,10 @@ impl CachedLspAdapter { ) -> Option { self.adapter.label_for_symbol(name, kind, language).await } + + pub fn enabled_formatters(&self) -> Vec { + self.adapter.enabled_formatters() + } } pub trait LspAdapterDelegate: Send + Sync { @@ -333,6 +337,26 @@ pub trait LspAdapter: 'static + Send + Sync { async fn language_ids(&self) -> HashMap { Default::default() } + + // TODO kb enable this for + // markdown somehow? + // tailwind (needs a css plugin, there are 2 of them) + fn enabled_formatters(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BundledFormatter { + Prettier { plugin_names: Vec }, +} + +impl BundledFormatter { + pub fn prettier() -> Self { + Self::Prettier { + plugin_names: Vec::new(), + } + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 844e0c7c36..8778ba8d64 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -47,7 +47,6 @@ pub struct LanguageSettings { pub show_wrap_guides: bool, pub wrap_guides: Vec, pub format_on_save: FormatOnSave, - pub prettier: bool, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, @@ -93,8 +92,6 @@ pub struct LanguageSettingsContent { #[serde(default)] pub format_on_save: Option, #[serde(default)] - pub prettier: Option, - #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, @@ -401,7 +398,6 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); - merge(&mut settings.prettier, src.prettier); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 56b86e5274..bfcce50c01 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,11 +37,11 @@ use language::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, split_operations, }, - range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction, - CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, - File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, - OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, - ToOffset, ToPointUtf16, Transaction, Unclipped, + range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, + CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, + Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, + LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, + TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use log::error; use lsp::{ @@ -2651,6 +2651,8 @@ impl Project { } } + // TODO kb 2 usages of this method (buffer language select + settings change) should take care of + // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8202,8 +8204,13 @@ impl Project { ) -> Option, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let language_settings = language_settings(buffer.language(), buffer_file, cx).clone(); - if !language_settings.prettier { + let buffer_language = buffer.language()?; + if !buffer_language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) + { return None; } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index fdbc179209..a28523a741 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for CssLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index b8f1c70cce..af8fb1690c 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -96,6 +96,10 @@ impl LspAdapter for HtmlLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 63f909ae2a..e66a5d96a6 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -4,7 +4,9 @@ use collections::HashMap; use feature_flags::FeatureFlagAppExt; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{ + BundledFormatter, LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate, +}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -144,6 +146,10 @@ impl LspAdapter for JsonLspAdapter { async fn language_ids(&self) -> HashMap { [("JSON".into(), "jsonc".into())].into_iter().collect() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index 3096fd16e6..f8f9351111 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -103,6 +103,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77..487720ce46 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,10 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 676d0fd4c0..78b1214b1a 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,7 +4,7 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -161,6 +161,10 @@ impl LspAdapter for TypeScriptLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_ts_server_binary( @@ -309,6 +313,10 @@ impl LspAdapter for EsLintLspAdapter { async fn initialization_options(&self) -> Option { None } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_eslint_server_binary( diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 8b438d0949..c9168b3480 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; use language::{ - language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate, + language_settings::all_language_settings, BundledFormatter, LanguageServerName, LspAdapter, + LspAdapterDelegate, }; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -108,6 +109,10 @@ impl LspAdapter for YamlLspAdapter { })) .boxed() } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::prettier()] + } } async fn get_cached_server_binary( From 1ff17bd15d23bf0b78424a24fbd448f4b97c5665 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 14:04:18 +0300 Subject: [PATCH 081/141] Install default prettier and plugins on startup --- crates/prettier/src/prettier.rs | 7 -- crates/project/src/project.rs | 124 +++++++++++++++++++++++++++++--- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 454f845aa4..d68af349d9 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -204,10 +204,3 @@ async fn find_closest_prettier_dir( } Ok(None) } - -async fn prepare_default_prettier( - fs: Arc, - node: Arc, -) -> anyhow::Result { - todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bfcce50c01..ffc15ee0d3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -20,7 +20,7 @@ use futures::{ mpsc::{self, UnboundedReceiver}, oneshot, }, - future::{try_join_all, Shared}, + future::{self, try_join_all, Shared}, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; @@ -31,7 +31,9 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, + language_settings::{ + language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings, + }, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -79,8 +81,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -832,17 +837,28 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); + let mut language_formatters_to_check = Vec::new(); for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - if let Some((file, language)) = buffer.file().zip(buffer.language()) { - let settings = language_settings(Some(language), Some(file), cx); + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let settings = language_settings(buffer_language, buffer_file, cx); + if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(Some(file)) { + if let Some(file) = File::from_dyn(buffer_file) { language_servers_to_start - .push((file.worktree.clone(), language.clone())); + .push((file.worktree.clone(), Arc::clone(language))); } } + let worktree = buffer_file + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + language_formatters_to_check.push(( + worktree, + Arc::clone(language), + settings.clone(), + )); } } } @@ -895,6 +911,11 @@ impl Project { .detach(); } + // TODO kb restart all formatters if settings change + for (worktree, language, settings) in language_formatters_to_check { + self.maybe_start_default_formatters(worktree, &language, &settings, cx); + } + // Start all the newly-enabled language servers. for (worktree, language) in language_servers_to_start { let worktree_path = worktree.read(cx).abs_path(); @@ -2643,7 +2664,15 @@ impl Project { } }); - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + let buffer_file = buffer.read(cx).file().cloned(); + let worktree = buffer_file + .as_ref() + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + + if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); @@ -2651,8 +2680,6 @@ impl Project { } } - // TODO kb 2 usages of this method (buffer language select + settings change) should take care of - // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8279,6 +8306,81 @@ impl Project { }); Some(task) } + + fn maybe_start_default_formatters( + &mut self, + worktree: Option, + new_language: &Language, + language_settings: &LanguageSettings, + cx: &mut ModelContext, + ) { + match &language_settings.formatter { + Formatter::Prettier { .. } | Formatter::Auto => {} + Formatter::LanguageServer | Formatter::External { .. } => return, + }; + let Some(node) = self.node.as_ref().cloned() else { + return; + }; + + let mut prettier_plugins = None; + for formatter in new_language + .lsp_adapters() + .into_iter() + .flat_map(|adapter| adapter.enabled_formatters()) + { + match formatter { + BundledFormatter::Prettier { plugin_names } => prettier_plugins + .get_or_insert_with(|| HashSet::default()) + .extend(plugin_names), + } + } + let Some(prettier_plugins) = prettier_plugins else { + return; + }; + + let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); + if let Some(_already_running) = self + .prettier_instances + .get(&(worktree, default_prettier_dir.to_path_buf())) + { + // TODO kb need to compare plugins, install missing and restart prettier + return; + } + + let fs = Arc::clone(&self.fs); + cx.background() + .spawn(async move { + let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; + if prettier_dir_metadata.is_none() { + fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; + } + + let packages_to_versions = future::try_join_all( + prettier_plugins + .iter() + .map(|s| s.as_str()) + .chain(Some("prettier")) + .map(|package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node.npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }), + ) + .await + .context("fetching latest npm versions")?; + + let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { + (package.as_str(), version.as_str()) + }).collect::>(); + node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } fn subscribe_for_copilot_events( From 86618a64c6ab00e51d569aaa99142d37e1583c3e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 12 Sep 2023 16:34:56 +0300 Subject: [PATCH 082/141] Require prettier argument and library in the wrapper --- .../prettier_server/package-lock.json | 35 ++++-------- crates/prettier/prettier_server/package.json | 2 +- crates/prettier/prettier_server/src/index.js | 53 ++++++++++++++++++- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json index 6d495d72b4..01fd75f3dd 100644 --- a/crates/prettier/prettier_server/package-lock.json +++ b/crates/prettier/prettier_server/package-lock.json @@ -1,29 +1,12 @@ { - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0", - "dependencies": { - "prettier": "^3.0.3" - } - }, - "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } + "name": "prettier_server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prettier_server", + "version": "1.0.0" + } } - } } diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json index 599f308e9f..a591a37bdb 100644 --- a/crates/prettier/prettier_server/package.json +++ b/crates/prettier/prettier_server/package.json @@ -8,7 +8,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Zed Industries", - "dependencies": { + "dev-dependencies": { "prettier": "^3.0" } } diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 5ac35d7ef9..482e996887 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,4 +1,36 @@ const { Buffer } = require('buffer'); +const fs = require("fs"); +const path = require("path"); + +let prettierContainerPath = process.argv[2]; +if (prettierContainerPath == null || prettierContainerPath.length == 0) { + console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.exit(1); +} +fs.stat(prettierContainerPath, (err, stats) => { + if (err) { + console.error(`Path '${prettierContainerPath}' does not exist.`); + process.exit(1); + } + + if (!stats.isDirectory()) { + console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.exit(1); + } +}); +let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + +(async () => { + let prettier; + try { + prettier = await loadPrettier(prettierPath); + } catch (error) { + console.error(error); + process.exit(1); + } + console.log("Prettier loadded successfully."); + // TODO kb do the rest here +})() let buffer = Buffer.alloc(0); process.stdin.resume(); @@ -28,14 +60,15 @@ function handleData() { try { const message = JSON.parse(bytes); handleMessage(message); - } catch (_) { - sendResponse(makeError("Request JSON parse error")); + } catch (e) { + sendResponse(makeError(`Request JSON parse error: ${e}`)); return; } } // format // clear_cache +// // shutdown // error @@ -55,3 +88,19 @@ function sendResponse(response) { process.stdout.write(length); process.stdout.write(message); } + +function loadPrettier(prettierPath) { + return new Promise((resolve, reject) => { + fs.access(prettierPath, fs.constants.F_OK, (err) => { + if (err) { + reject(`Path '${prettierPath}' does not exist.Error: ${err}`); + } else { + try { + resolve(require(prettierPath)); + } catch (err) { + reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`); + } + } + }); + }); +} From bb2cc2d157a5f3aa4a8a715d6eeb9df97f4f4d3d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 17:14:22 +0300 Subject: [PATCH 083/141] Async-ify prettier wrapper --- crates/prettier/prettier_server/src/index.js | 100 +++++++++++++------ 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 482e996887..15aeb30dca 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -1,8 +1,9 @@ const { Buffer } = require('buffer'); const fs = require("fs"); const path = require("path"); +const { once } = require('events'); -let prettierContainerPath = process.argv[2]; +const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); @@ -18,48 +19,83 @@ fs.stat(prettierContainerPath, (err, stats) => { process.exit(1); } }); -let prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); + (async () => { let prettier; try { prettier = await loadPrettier(prettierPath); } catch (error) { - console.error(error); + console.error("Failed to load prettier: ", error); process.exit(1); } console.log("Prettier loadded successfully."); - // TODO kb do the rest here + process.stdin.resume(); + handleBuffer(prettier); })() -let buffer = Buffer.alloc(0); -process.stdin.resume(); -process.stdin.on('data', (data) => { - buffer = Buffer.concat([buffer, data]); - handleData(); -}); -process.stdin.on('end', () => { - handleData(); -}); - -function handleData() { - if (buffer.length < 4) { - return; +async function handleBuffer(prettier) { + for await (let messageText of readStdin()) { + handleData(messageText, prettier).catch(e => { + console.error("Failed to handle formatter request", e); + }); } +} - const length = buffer.readUInt32LE(0); - console.log(length); - console.log(buffer.toString()); - if (buffer.length < 4 + length) { - return; - } - - const bytes = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); +async function* readStdin() { + const bufferLengthOffset = 4; + let buffer = Buffer.alloc(0); + let streamEnded = false; + process.stdin.on('end', () => { + streamEnded = true; + }); + process.stdin.on('data', (data) => { + buffer = Buffer.concat([buffer, data]); + }); try { - const message = JSON.parse(bytes); - handleMessage(message); + main_loop: while (true) { + while (buffer.length < bufferLengthOffset) { + if (streamEnded) { + sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const length = buffer.readUInt32LE(0); + + while (buffer.length < (bufferLengthOffset + length)) { + if (streamEnded) { + sendResponse(makeError( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); + buffer = Buffer.alloc(0); + streamEnded = false; + await once(process.stdin, 'readable'); + continue main_loop; + } + await once(process.stdin, 'readable'); + } + + const message = buffer.subarray(4, 4 + length); + buffer = buffer.subarray(4 + length); + yield message.toString('utf8'); + } + } catch (e) { + console.error(`Error reading stdin: ${e}`); + } finally { + process.stdin.off('data'); + } +} + +async function handleData(messageText, prettier) { + try { + const message = JSON.parse(messageText); + await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); return; @@ -72,8 +108,8 @@ function handleData() { // shutdown // error -function handleMessage(message) { - console.log(message); +async function handleMessage(prettier, message) { + console.log(`message: ${message}`); sendResponse({ method: "hi", result: null }); } @@ -82,8 +118,8 @@ function makeError(message) { } function sendResponse(response) { - let message = Buffer.from(JSON.stringify(response)); - let length = Buffer.alloc(4); + const message = Buffer.from(JSON.stringify(response)); + const length = Buffer.alloc(4); length.writeUInt32LE(message.length); process.stdout.write(length); process.stdout.write(message); From 010bb73ac2a28903a0822486d34b00042c868d7b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Sep 2023 23:10:42 +0300 Subject: [PATCH 084/141] Use LSP-like protocol for prettier wrapper commands --- crates/prettier/prettier_server/src/index.js | 71 +++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/prettier_server/src/index.js index 15aeb30dca..d232ac9efe 100644 --- a/crates/prettier/prettier_server/src/index.js +++ b/crates/prettier/prettier_server/src/index.js @@ -44,7 +44,6 @@ async function handleBuffer(prettier) { } async function* readStdin() { - const bufferLengthOffset = 4; let buffer = Buffer.alloc(0); let streamEnded = false; process.stdin.on('end', () => { @@ -54,41 +53,63 @@ async function* readStdin() { buffer = Buffer.concat([buffer, data]); }); + async function handleStreamEnded(errorMessage) { + sendResponse(makeError(errorMessage)); + buffer = Buffer.alloc(0); + messageLength = null; + await once(process.stdin, 'readable'); + streamEnded = false; + } + try { + const headersSeparator = "\r\n\r\n"; + let contentLengthHeaderName = 'Content-Length'; + let headersLength = null; + let messageLength = null; main_loop: while (true) { - while (buffer.length < bufferLengthOffset) { - if (streamEnded) { - sendResponse(makeError(`Unexpected end of stream: less than ${bufferLengthOffset} characters passed`)); - buffer = Buffer.alloc(0); - streamEnded = false; + if (messageLength === null) { + while (buffer.indexOf(headersSeparator) === -1) { + if (streamEnded) { + await handleStreamEnded('Unexpected end of stream: headers not found'); + continue main_loop; + } else if (buffer.length > contentLengthHeaderName.length * 10) { + await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`); + continue main_loop; + } await once(process.stdin, 'readable'); + } + const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); + const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + .filter(header => header[2] === undefined) + .filter(header => (header[1] || '').length > 0) + .find(header => header[0].trim() === contentLengthHeaderName); + if (contentLengthHeader === undefined) { + await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); + continue main_loop; + } + headersLength = headers.length + headersSeparator.length; + messageLength = parseInt(contentLengthHeader[1], 10); + } + + while (buffer.length < (headersLength + messageLength)) { + if (streamEnded) { + await handleStreamEnded( + `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`); continue main_loop; } await once(process.stdin, 'readable'); } - const length = buffer.readUInt32LE(0); - - while (buffer.length < (bufferLengthOffset + length)) { - if (streamEnded) { - sendResponse(makeError( - `Unexpected end of stream: buffer length ${buffer.length} does not match expected length ${bufferLengthOffset} + ${length}`)); - buffer = Buffer.alloc(0); - streamEnded = false; - await once(process.stdin, 'readable'); - continue main_loop; - } - await once(process.stdin, 'readable'); - } - - const message = buffer.subarray(4, 4 + length); - buffer = buffer.subarray(4 + length); + const messageEnd = headersLength + messageLength; + const message = buffer.subarray(headersLength, messageEnd); + buffer = buffer.subarray(messageEnd); + messageLength = null; yield message.toString('utf8'); } } catch (e) { console.error(`Error reading stdin: ${e}`); } finally { - process.stdin.off('data'); + process.stdin.off('data', () => { }); } } @@ -98,7 +119,6 @@ async function handleData(messageText, prettier) { await handleMessage(prettier, message); } catch (e) { sendResponse(makeError(`Request JSON parse error: ${e}`)); - return; } } @@ -109,7 +129,8 @@ async function handleData(messageText, prettier) { // error async function handleMessage(prettier, message) { - console.log(`message: ${message}`); + // TODO kb handle message.method, message.params and message.id + console.log(`message: ${JSON.stringify(message)}`); sendResponse({ method: "hi", result: null }); } From dca93fb1770f658a1fd92a2ced73f66553dff1cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 17 Sep 2023 01:21:46 +0300 Subject: [PATCH 085/141] Initialize prettier_server.js wrapper along with default prettier --- crates/prettier/prettier_server/.gitignore | 1 - crates/prettier/prettier_server/.zed/settings.json | 7 ------- crates/prettier/prettier_server/package-lock.json | 12 ------------ crates/prettier/prettier_server/package.json | 14 -------------- crates/prettier/src/prettier.rs | 2 ++ .../src/index.js => src/prettier_server.js} | 0 crates/project/src/project.rs | 10 +++++----- 7 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 crates/prettier/prettier_server/.gitignore delete mode 100644 crates/prettier/prettier_server/.zed/settings.json delete mode 100644 crates/prettier/prettier_server/package-lock.json delete mode 100644 crates/prettier/prettier_server/package.json rename crates/prettier/{prettier_server/src/index.js => src/prettier_server.js} (100%) diff --git a/crates/prettier/prettier_server/.gitignore b/crates/prettier/prettier_server/.gitignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/crates/prettier/prettier_server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/crates/prettier/prettier_server/.zed/settings.json b/crates/prettier/prettier_server/.zed/settings.json deleted file mode 100644 index 856536c62d..0000000000 --- a/crates/prettier/prettier_server/.zed/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "languages": { - "JavaScript": { - "tab_size": 4 - } - } -} diff --git a/crates/prettier/prettier_server/package-lock.json b/crates/prettier/prettier_server/package-lock.json deleted file mode 100644 index 01fd75f3dd..0000000000 --- a/crates/prettier/prettier_server/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "prettier_server", - "version": "1.0.0" - } - } -} diff --git a/crates/prettier/prettier_server/package.json b/crates/prettier/prettier_server/package.json deleted file mode 100644 index a591a37bdb..0000000000 --- a/crates/prettier/prettier_server/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "prettier_server", - "version": "1.0.0", - "description": "", - "main": "src/index.js", - "scripts": { - "start": "node src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Zed Industries", - "dev-dependencies": { - "prettier": "^3.0" - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d68af349d9..e5b001f226 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -18,6 +18,8 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); + impl Prettier { // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ diff --git a/crates/prettier/prettier_server/src/index.js b/crates/prettier/src/prettier_server.js similarity index 100% rename from crates/prettier/prettier_server/src/index.js rename to crates/prettier/src/prettier_server.js diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ffc15ee0d3..b3d00afd8a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -79,7 +79,7 @@ use std::{ time::{Duration, Instant}, }; use terminals::Terminals; -use text::Anchor; +use text::{Anchor, LineEnding, Rope}; use util::{ debug_panic, defer, http::HttpClient, @@ -8350,10 +8350,10 @@ impl Project { let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; - if prettier_dir_metadata.is_none() { - fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; - } + let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + // method creates parent directory if it doesn't exist + fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 2a68f014027b501d1a15cdfbf58358540543b086 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 14:56:40 +0300 Subject: [PATCH 086/141] Draft prettier_server formatting --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 85 +++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 86 ++++++++++++++++---------- crates/project/src/project.rs | 25 +++----- 5 files changed, 139 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d7e250377..1795d3d748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5527,6 +5527,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "lsp", "node_runtime", "serde", "serde_derive", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index ab8d4d9e16..d10e8abdf9 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -10,6 +10,7 @@ path = "src/prettier.rs" language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } +lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index e5b001f226..fc7736b0e0 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,12 +4,15 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::ModelHandle; +use gpui::{AsyncAppContext, ModelHandle, Task}; use language::{Buffer, Diff}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; +use serde::{Deserialize, Serialize}; +use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { - _private: (), + server: Arc, } #[derive(Debug)] @@ -18,7 +21,9 @@ pub struct LocateStart { pub starting_path: Arc, } +pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); +const PRETTIER_PACKAGE_NAME: &str = "prettier"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -141,16 +146,55 @@ impl Prettier { } } - pub async fn start(prettier_dir: &Path, node: Arc) -> anyhow::Result { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}") + pub fn start( + prettier_dir: PathBuf, + node: Arc, + cx: AsyncAppContext, + ) -> Task> { + cx.spawn(|cx| async move { + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); + + let node_path = node.binary_path().await?; + let server = LanguageServer::new( + LanguageServerId(0), + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = server + .initialize(None) + .await + .context("prettier server initialization")?; + Ok(Self { server }) + }) } - pub async fn format(&self, buffer: &ModelHandle) -> anyhow::Result { - todo!() + pub async fn format( + &self, + buffer: &ModelHandle, + cx: &AsyncAppContext, + ) -> anyhow::Result { + let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let response = self + .server + .request::(PrettierFormatParams { text: buffer_text }) + .await + .context("prettier format request")?; + dbg!("Formatted text", response.text); + anyhow::bail!("TODO kb calculate the diff") } pub async fn clear_cache(&self) -> anyhow::Result<()> { @@ -158,7 +202,6 @@ impl Prettier { } } -const PRETTIER_PACKAGE_NAME: &str = "prettier"; async fn find_closest_prettier_dir( paths_to_check: Vec, fs: &dyn Fs, @@ -206,3 +249,23 @@ async fn find_closest_prettier_dir( } Ok(None) } + +enum PrettierFormat {} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatParams { + text: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct PrettierFormatResult { + text: String, +} + +impl lsp::request::Request for PrettierFormat { + type Params = PrettierFormatParams; + type Result = PrettierFormatResult; + const METHOD: &'static str = "prettier/format"; +} diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index d232ac9efe..0caa45b917 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - console.error(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - console.error(`Path '${prettierContainerPath}' does not exist.`); + sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); process.exit(1); } if (!stats.isDirectory()) { - console.log(`Path '${prettierContainerPath}' exists but is not a directory.`); + sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); process.exit(1); } }); @@ -26,19 +26,19 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); let prettier; try { prettier = await loadPrettier(prettierPath); - } catch (error) { - console.error("Failed to load prettier: ", error); + } catch (e) { + sendResponse(makeError(`Failed to load prettier: ${e}`)); process.exit(1); } - console.log("Prettier loadded successfully."); + sendResponse(makeError("Prettier loadded successfully.")); process.stdin.resume(); handleBuffer(prettier); })() async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleData(messageText, prettier).catch(e => { - console.error("Failed to handle formatter request", e); + handleMessage(messageText, prettier).catch(e => { + sendResponse(makeError(`error during message handling: ${e}`)); }); } } @@ -107,43 +107,63 @@ async function* readStdin() { yield message.toString('utf8'); } } catch (e) { - console.error(`Error reading stdin: ${e}`); + sendResponse(makeError(`Error reading stdin: ${e}`)); } finally { process.stdin.off('data', () => { }); } } -async function handleData(messageText, prettier) { - try { - const message = JSON.parse(messageText); - await handleMessage(prettier, message); - } catch (e) { - sendResponse(makeError(`Request JSON parse error: ${e}`)); +// ? +// shutdown +// error +async function handleMessage(messageText, prettier) { + const message = JSON.parse(messageText); + const { method, id, params } = message; + if (method === undefined) { + throw new Error(`Message method is undefined: ${messageText}`); + } + if (id === undefined) { + throw new Error(`Message id is undefined: ${messageText}`); + } + + if (method === 'prettier/format') { + if (params === undefined || params.text === undefined) { + throw new Error(`Message params.text is undefined: ${messageText}`); + } + let formattedText = await prettier.format(params.text); + sendResponse({ id, result: { text: formattedText } }); + } else if (method === 'prettier/clear_cache') { + prettier.clearConfigCache(); + sendResponse({ id, result: null }); + } else if (method === 'initialize') { + sendResponse({ + id, + result: { + "capabilities": {} + } + }); + } else { + throw new Error(`Unknown method: ${method}`); } } -// format -// clear_cache -// -// shutdown -// error - -async function handleMessage(prettier, message) { - // TODO kb handle message.method, message.params and message.id - console.log(`message: ${JSON.stringify(message)}`); - sendResponse({ method: "hi", result: null }); -} - function makeError(message) { - return { method: "error", message }; + return { + error: { + "code": -32600, // invalid request code + message, + } + }; } function sendResponse(response) { - const message = Buffer.from(JSON.stringify(response)); - const length = Buffer.alloc(4); - length.writeUInt32LE(message.length); - process.stdout.write(length); - process.stdout.write(message); + let responsePayloadString = JSON.stringify({ + jsonrpc: "2.0", + ...response + }); + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let dataToSend = headers + responsePayloadString; + process.stdout.write(dataToSend); } function loadPrettier(prettierPath) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3d00afd8a..84b5f09cac 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -53,7 +53,7 @@ use lsp::{ use lsp_command::*; use node_runtime::NodeRuntime; use postage::watch; -use prettier::{LocateStart, Prettier}; +use prettier::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -4138,7 +4138,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -4176,7 +4176,7 @@ impl Project { Ok(prettier) => { format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer) + .format(buffer, &cx) .await .context("formatting via prettier")?, )); @@ -8283,18 +8283,12 @@ impl Project { return existing_prettier; } - let task_prettier_dir = prettier_dir.clone(); + let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); let new_prettier_task = cx .background() .spawn(async move { - Ok(Arc::new( - Prettier::start(&task_prettier_dir, node) - .await - .with_context(|| { - format!("starting new prettier for path {task_prettier_dir:?}") - })?, - )) - .map_err(Arc::new) + Ok(Arc::new(start_task.await.context("starting new prettier")?)) + .map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { @@ -8344,16 +8338,17 @@ impl Project { .get(&(worktree, default_prettier_dir.to_path_buf())) { // TODO kb need to compare plugins, install missing and restart prettier + // TODO kb move the entire prettier init logic into prettier.rs return; } let fs = Arc::clone(&self.fs); cx.background() .spawn(async move { - let prettier_wrapper_path = default_prettier_dir.join("prettier_server.js"); + let prettier_wrapper_path = default_prettier_dir.join(PRETTIER_SERVER_FILE); // method creates parent directory if it doesn't exist - fs.save(&prettier_wrapper_path, &Rope::from(prettier::PRETTIER_SERVER_JS), LineEnding::Unix).await - .with_context(|| format!("writing prettier_server.js file at {prettier_wrapper_path:?}"))?; + fs.save(&prettier_wrapper_path, &Rope::from(PRETTIER_SERVER_JS), LineEnding::Unix).await + .with_context(|| format!("writing {PRETTIER_SERVER_FILE} file at {prettier_wrapper_path:?}"))?; let packages_to_versions = future::try_join_all( prettier_plugins From 6a8e3fd02d4cd1936b277cd3a40ddac4a076f0fe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:16:16 +0300 Subject: [PATCH 087/141] Add more parameters into prettier invocations --- crates/prettier/src/prettier.rs | 12 ++++++++++-- crates/prettier/src/prettier_server.js | 24 ++++++++++++++++-------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index fc7736b0e0..180b9c3de5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,10 +187,15 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); + let (buffer_text, buffer_language) = + buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self .server - .request::(PrettierFormatParams { text: buffer_text }) + .request::(PrettierFormatParams { + text: buffer_text, + path: None, + parser: None, + }) .await .context("prettier format request")?; dbg!("Formatted text", response.text); @@ -256,6 +261,9 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, + // TODO kb have "options" or something more generic instead? + parser: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0caa45b917..a0f7f0da5e 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -43,6 +43,8 @@ async function handleBuffer(prettier) { } } +const headerSeparator = "\r\n"; + async function* readStdin() { let buffer = Buffer.alloc(0); let streamEnded = false; @@ -62,13 +64,12 @@ async function* readStdin() { } try { - const headersSeparator = "\r\n\r\n"; let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { if (messageLength === null) { - while (buffer.indexOf(headersSeparator) === -1) { + while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) { if (streamEnded) { await handleStreamEnded('Unexpected end of stream: headers not found'); continue main_loop; @@ -78,16 +79,16 @@ async function* readStdin() { } await once(process.stdin, 'readable'); } - const headers = buffer.subarray(0, buffer.indexOf(headersSeparator)).toString('ascii'); - const contentLengthHeader = headers.split('\r\n').map(header => header.split(': ')) + const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); + const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) .find(header => header[0].trim() === contentLengthHeaderName); if (contentLengthHeader === undefined) { - await handleStreamEnded(`Missing or incorrect Content-Length header: ${headers}`); + await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } - headersLength = headers.length + headersSeparator.length; + headersLength = headers.length + headerSeparator.length * 2; messageLength = parseInt(contentLengthHeader[1], 10); } @@ -130,7 +131,14 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } - let formattedText = await prettier.format(params.text); + + let options = {}; + if (message.path !== undefined) { + options.filepath = message.path; + } else { + options.parser = message.parser || 'babel'; + } + const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); @@ -161,7 +169,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}\r\n\r\n`; + let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From e2056756ef97af2add0221ad8916315939d476db Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 17:34:58 +0300 Subject: [PATCH 088/141] Calculate the diff --- crates/prettier/src/prettier.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 180b9c3de5..f94801c01b 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -187,6 +187,7 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { + // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format let (buffer_text, buffer_language) = buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); let response = self @@ -198,8 +199,8 @@ impl Prettier { }) .await .context("prettier format request")?; - dbg!("Formatted text", response.text); - anyhow::bail!("TODO kb calculate the diff") + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) } pub async fn clear_cache(&self) -> anyhow::Result<()> { From 2a5b9b635ba1d250453984f9f8dc7fef32811d9d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:08:55 +0300 Subject: [PATCH 089/141] Better pass prettier options --- crates/language/src/language.rs | 9 ++++-- crates/prettier/src/prettier.rs | 41 +++++++++++++++++++------- crates/prettier/src/prettier_server.js | 12 +++++--- crates/project/src/project.rs | 2 +- crates/zed/src/languages/css.rs | 2 +- crates/zed/src/languages/html.rs | 2 +- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/languages/php.rs | 7 ++--- crates/zed/src/languages/svelte.rs | 6 +--- crates/zed/src/languages/typescript.rs | 4 +-- crates/zed/src/languages/yaml.rs | 2 +- 11 files changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index ed0a0d8ee8..31415a2dd6 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -341,6 +341,7 @@ pub trait LspAdapter: 'static + Send + Sync { // TODO kb enable this for // markdown somehow? // tailwind (needs a css plugin, there are 2 of them) + // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -348,12 +349,16 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { - Prettier { plugin_names: Vec }, + Prettier { + parser_name: &'static str, + plugin_names: Vec, + }, } impl BundledFormatter { - pub fn prettier() -> Self { + pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { + parser_name, plugin_names: Vec::new(), } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index f94801c01b..68374d8a03 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; -use language::{Buffer, Diff}; +use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -187,16 +187,30 @@ impl Prettier { buffer: &ModelHandle, cx: &AsyncAppContext, ) -> anyhow::Result { - // TODO kb prettier needs either a path or a `parser` (depends on a language) option to format - let (buffer_text, buffer_language) = - buffer.read_with(cx, |buffer, _| (buffer.text(), buffer.language().cloned())); + let params = buffer.read_with(cx, |buffer, cx| { + let path = buffer + .file() + .map(|file| file.full_path(cx)) + .map(|path| path.to_path_buf()); + let parser = buffer.language().and_then(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .find_map(|formatter| match formatter { + BundledFormatter::Prettier { parser_name, .. } => { + Some(parser_name.to_string()) + } + }) + }); + PrettierFormatParams { + text: buffer.text(), + options: FormatOptions { parser, path }, + } + }); let response = self .server - .request::(PrettierFormatParams { - text: buffer_text, - path: None, - parser: None, - }) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -262,9 +276,14 @@ enum PrettierFormat {} #[serde(rename_all = "camelCase")] struct PrettierFormatParams { text: String, - // TODO kb have "options" or something more generic instead? + options: FormatOptions, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct FormatOptions { parser: Option, - path: Option, + path: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index a0f7f0da5e..5e86720cd8 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -131,12 +131,16 @@ async function handleMessage(messageText, prettier) { if (params === undefined || params.text === undefined) { throw new Error(`Message params.text is undefined: ${messageText}`); } + if (params.options === undefined) { + throw new Error(`Message params.options is undefined: ${messageText}`); + } let options = {}; - if (message.path !== undefined) { - options.filepath = message.path; - } else { - options.parser = message.parser || 'babel'; + if (params.options.path !== undefined) { + options.filepath = params.options.path; + } + if (params.options.parser !== undefined) { + options.parser = params.options.parser; } const formattedText = await prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 84b5f09cac..cf8df9eafd 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8323,7 +8323,7 @@ impl Project { .flat_map(|adapter| adapter.enabled_formatters()) { match formatter { - BundledFormatter::Prettier { plugin_names } => prettier_plugins + BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins .get_or_insert_with(|| HashSet::default()) .extend(plugin_names), } diff --git a/crates/zed/src/languages/css.rs b/crates/zed/src/languages/css.rs index a28523a741..f046437d75 100644 --- a/crates/zed/src/languages/css.rs +++ b/crates/zed/src/languages/css.rs @@ -98,7 +98,7 @@ impl LspAdapter for CssLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("css")] } } diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index af8fb1690c..6f27b7ca8f 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -98,7 +98,7 @@ impl LspAdapter for HtmlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("html")] } } diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index e66a5d96a6..f017af0a22 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -148,7 +148,7 @@ impl LspAdapter for JsonLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("json")] } } diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index f8f9351111..bf65deb642 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; @@ -100,13 +100,10 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } + async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 487720ce46..5e42d80e77 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,10 +95,6 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } - - fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] - } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 78b1214b1a..f09c964588 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -163,7 +163,7 @@ impl LspAdapter for TypeScriptLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("typescript")] } } @@ -315,7 +315,7 @@ impl LspAdapter for EsLintLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("babel")] } } diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c9168b3480..1c1ce18668 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -111,7 +111,7 @@ impl LspAdapter for YamlLspAdapter { } fn enabled_formatters(&self) -> Vec { - vec![BundledFormatter::prettier()] + vec![BundledFormatter::prettier("yaml")] } } From 39ad3a625c29f32f30a032b70e30ff7bd0811eb8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Sep 2023 18:36:18 +0300 Subject: [PATCH 090/141] Generify prettier properties, add tabWidth --- crates/prettier/src/prettier.rs | 18 ++++++++++++++---- crates/prettier/src/prettier_server.js | 10 +--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 68374d8a03..a02b139510 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle, Task}; +use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; @@ -188,11 +189,13 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let path = buffer - .file() + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let language_settings = language_settings(buffer_language, buffer_file, cx); + let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer.language().and_then(|language| { + let parser = buffer_language.and_then(|language| { language .lsp_adapters() .iter() @@ -203,9 +206,14 @@ impl Prettier { } }) }); + let tab_width = Some(language_settings.tab_size.get()); PrettierFormatParams { text: buffer.text(), - options: FormatOptions { parser, path }, + options: FormatOptions { + parser, + path, + tab_width, + }, } }); let response = self @@ -283,7 +291,9 @@ struct PrettierFormatParams { #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, + #[serde(rename = "filepath")] path: Option, + tab_width: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5e86720cd8..2725914334 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -134,15 +134,7 @@ async function handleMessage(messageText, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${messageText}`); } - - let options = {}; - if (params.options.path !== undefined) { - options.filepath = params.options.path; - } - if (params.options.parser !== undefined) { - options.parser = params.options.parser; - } - const formattedText = await prettier.format(params.text, options); + const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.clearConfigCache(); From e8409a0108f719a624af2113f2c8cf7f94bc6fcc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 19 Sep 2023 10:25:33 +0300 Subject: [PATCH 091/141] Even more generic header printing in prettier_server --- crates/prettier/src/prettier_server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 2725914334..02c21811e2 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -44,6 +44,7 @@ async function handleBuffer(prettier) { } const headerSeparator = "\r\n"; +const contentLengthHeaderName = 'Content-Length'; async function* readStdin() { let buffer = Buffer.alloc(0); @@ -64,7 +65,6 @@ async function* readStdin() { } try { - let contentLengthHeaderName = 'Content-Length'; let headersLength = null; let messageLength = null; main_loop: while (true) { @@ -165,7 +165,7 @@ function sendResponse(response) { jsonrpc: "2.0", ...response }); - let headers = `Content-Length: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; let dataToSend = headers + responsePayloadString; process.stdout.write(dataToSend); } From 4b15a2bd63c1e702178ff83b15ea29dafb437c5e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 12:19:12 +0300 Subject: [PATCH 092/141] Rebase fixes --- crates/semantic_index/examples/eval.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/semantic_index/examples/eval.rs b/crates/semantic_index/examples/eval.rs index 573cf73d78..33d6b3689c 100644 --- a/crates/semantic_index/examples/eval.rs +++ b/crates/semantic_index/examples/eval.rs @@ -494,6 +494,7 @@ fn main() { let project = cx.update(|cx| { Project::local( client.clone(), + node_runtime::FakeNodeRuntime::new(), user_store.clone(), languages.clone(), fs.clone(), From 6cac58b34c67507d555ffccda6a04d86a312e47c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 13:07:10 +0300 Subject: [PATCH 093/141] Add prettier language servers to LSP logs panel --- crates/prettier/src/prettier.rs | 68 ++++++++++++++++++--------------- crates/project/src/project.rs | 27 ++++++++++--- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a02b139510..26368ddcdc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Context; use fs::Fs; -use gpui::{AsyncAppContext, ModelHandle, Task}; +use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -147,40 +147,42 @@ impl Prettier { } } - pub fn start( + pub async fn start( + server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, cx: AsyncAppContext, - ) -> Task> { - cx.spawn(|cx| async move { - anyhow::ensure!( - prettier_dir.is_dir(), - "Prettier dir {prettier_dir:?} is not a directory" - ); - let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); - anyhow::ensure!( - prettier_server.is_file(), - "no prettier server package found at {prettier_server:?}" - ); + ) -> anyhow::Result { + let backgroud = cx.background(); + anyhow::ensure!( + prettier_dir.is_dir(), + "Prettier dir {prettier_dir:?} is not a directory" + ); + let prettier_server = DEFAULT_PRETTIER_DIR.join(PRETTIER_SERVER_FILE); + anyhow::ensure!( + prettier_server.is_file(), + "no prettier server package found at {prettier_server:?}" + ); - let node_path = node.binary_path().await?; - let server = LanguageServer::new( - LanguageServerId(0), - LanguageServerBinary { - path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], - }, - Path::new("/"), - None, - cx, - ) - .context("prettier server creation")?; - let server = server - .initialize(None) - .await - .context("prettier server initialization")?; - Ok(Self { server }) - }) + let node_path = backgroud + .spawn(async move { node.binary_path().await }) + .await?; + let server = LanguageServer::new( + server_id, + LanguageServerBinary { + path: node_path, + arguments: vec![prettier_server.into(), prettier_dir.into()], + }, + Path::new("/"), + None, + cx, + ) + .context("prettier server creation")?; + let server = backgroud + .spawn(server.initialize(None)) + .await + .context("prettier server initialization")?; + Ok(Self { server }) } pub async fn format( @@ -228,6 +230,10 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { todo!() } + + pub fn server(&self) -> &Arc { + &self.server + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cf8df9eafd..8e080b594e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8283,12 +8283,29 @@ impl Project { return existing_prettier; } - let start_task = Prettier::start(prettier_dir.clone(), node, cx.clone()); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx - .background() - .spawn(async move { - Ok(Arc::new(start_task.await.context("starting new prettier")?)) - .map_err(Arc::new) + .spawn(|mut cx| async move { + let prettier = + Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) + .await + .context("prettier start") + .map_err(Arc::new)?; + if let Some(project) = weak_project.upgrade(&mut cx) { + let prettier_server = Arc::clone(prettier.server()); + project.update(&mut cx, |project, cx| { + project.supplementary_language_servers.insert( + new_server_id, + // TODO kb same name repeats for different prettiers, distinguish + (LanguageServerName(Arc::from("prettier")), prettier_server), + ); + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) }) .shared(); this.update(&mut cx, |project, _| { From 06cac18d788c1373d1e38d22c9fcc0fe21ac6546 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:51:02 +0300 Subject: [PATCH 094/141] Return message id in prettier_server error responses --- crates/prettier/src/prettier_server.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 02c21811e2..2e7e1fd2e6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -37,8 +37,15 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); async function handleBuffer(prettier) { for await (let messageText of readStdin()) { - handleMessage(messageText, prettier).catch(e => { - sendResponse(makeError(`error during message handling: ${e}`)); + let message; + try { + message = JSON.parse(messageText); + } catch (e) { + sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); + continue; + } + handleMessage(message, prettier).catch(e => { + sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); } } @@ -117,22 +124,21 @@ async function* readStdin() { // ? // shutdown // error -async function handleMessage(messageText, prettier) { - const message = JSON.parse(messageText); +async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { - throw new Error(`Message method is undefined: ${messageText}`); + throw new Error(`Message method is undefined: ${JSON.stringify(message)}`); } if (id === undefined) { - throw new Error(`Message id is undefined: ${messageText}`); + throw new Error(`Message id is undefined: ${JSON.stringify(message)}`); } if (method === 'prettier/format') { if (params === undefined || params.text === undefined) { - throw new Error(`Message params.text is undefined: ${messageText}`); + throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`); } if (params.options === undefined) { - throw new Error(`Message params.options is undefined: ${messageText}`); + throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } const formattedText = await prettier.format(params.text, params.options); sendResponse({ id, result: { text: formattedText } }); From b6872702070c7f7681324df94ebf8721a28172d5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 14:54:20 +0300 Subject: [PATCH 095/141] Implement missing prettier_server clear method --- crates/prettier/src/prettier.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 26368ddcdc..d5df6f092c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -209,7 +209,7 @@ impl Prettier { }) }); let tab_width = Some(language_settings.tab_size.get()); - PrettierFormatParams { + FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -220,7 +220,7 @@ impl Prettier { }); let response = self .server - .request::(params) + .request::(params) .await .context("prettier format request")?; let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); @@ -228,7 +228,10 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - todo!() + self.server + .request::(()) + .await + .context("prettier clear cache") } pub fn server(&self) -> &Arc { @@ -284,11 +287,11 @@ async fn find_closest_prettier_dir( Ok(None) } -enum PrettierFormat {} +enum Format {} #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatParams { +struct FormatParams { text: String, options: FormatOptions, } @@ -304,12 +307,20 @@ struct FormatOptions { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct PrettierFormatResult { +struct FormatResult { text: String, } -impl lsp::request::Request for PrettierFormat { - type Params = PrettierFormatParams; - type Result = PrettierFormatResult; +impl lsp::request::Request for Format { + type Params = FormatParams; + type Result = FormatResult; const METHOD: &'static str = "prettier/format"; } + +enum ClearCache {} + +impl lsp::request::Request for ClearCache { + type Params = (); + type Result = (); + const METHOD: &'static str = "prettier/clear_cache"; +} From 1b70e7d0df21d74f5133636580550d33fa4fbb28 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 20 Sep 2023 15:00:26 +0300 Subject: [PATCH 096/141] Before server startup, log to stderr --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 2e7e1fd2e6..e5c131cc00 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - sendResponse(makeError(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`)); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - sendResponse(makeError(`Path '${prettierContainerPath}' does not exist.`)); + process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); process.exit(1); } if (!stats.isDirectory()) { - sendResponse(makeError(`Path '${prettierContainerPath}' exists but is not a directory.`)); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - sendResponse(makeError(`Failed to load prettier: ${e}`)); + process.stderr.write(`Failed to load prettier: ${e}`); process.exit(1); } - sendResponse(makeError("Prettier loadded successfully.")); + process.stderr.write("Prettier loadded successfully."); process.stdin.resume(); handleBuffer(prettier); })() From f42cb109a0f115bed92720721c4cf59a73f93574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 15:46:50 +0300 Subject: [PATCH 097/141] Improve prettier_server LSP names in the log panel --- crates/prettier/src/prettier.rs | 27 ++++++++++++-- crates/project/src/project.rs | 63 +++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index d5df6f092c..5723cc67fc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -13,6 +13,9 @@ use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub struct Prettier { + worktree_id: Option, + default: bool, + prettier_dir: PathBuf, server: Arc, } @@ -143,11 +146,12 @@ impl Prettier { .with_context(|| format!("finding prettier starting with {starting_path:?}"))? { Some(prettier_dir) => Ok(prettier_dir), - None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()), + None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()), } } pub async fn start( + worktree_id: Option, server_id: LanguageServerId, prettier_dir: PathBuf, node: Arc, @@ -171,7 +175,7 @@ impl Prettier { server_id, LanguageServerBinary { path: node_path, - arguments: vec![prettier_server.into(), prettier_dir.into()], + arguments: vec![prettier_server.into(), prettier_dir.as_path().into()], }, Path::new("/"), None, @@ -182,7 +186,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { server }) + Ok(Self { + worktree_id, + server, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }) } pub async fn format( @@ -237,6 +246,18 @@ impl Prettier { pub fn server(&self) -> &Arc { &self.server } + + pub fn is_default(&self) -> bool { + self.default + } + + pub fn prettier_dir(&self) -> &Path { + &self.prettier_dir + } + + pub fn worktree_id(&self) -> Option { + self.worktree_id + } } async fn find_closest_prettier_dir( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8e080b594e..2afedecad6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8289,19 +8289,60 @@ impl Project { this.update(&mut cx, |this, _| this.languages.next_language_server_id()); let new_prettier_task = cx .spawn(|mut cx| async move { - let prettier = - Prettier::start(new_server_id, task_prettier_dir, node, cx.clone()) - .await - .context("prettier start") - .map_err(Arc::new)?; + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; if let Some(project) = weak_project.upgrade(&mut cx) { - let prettier_server = Arc::clone(prettier.server()); project.update(&mut cx, |project, cx| { - project.supplementary_language_servers.insert( - new_server_id, - // TODO kb same name repeats for different prettiers, distinguish - (LanguageServerName(Arc::from("prettier")), prettier_server), - ); + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } + } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), + } + }; + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier.server()))); cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From d021842fa1ff8dcc76f00e5b5a1950134edb11c9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Sep 2023 23:49:22 +0300 Subject: [PATCH 098/141] Properly log pre-lsp prettier_server events --- crates/prettier/src/prettier_server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index e5c131cc00..001adca474 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -5,17 +5,17 @@ const { once } = require('events'); const prettierContainerPath = process.argv[2]; if (prettierContainerPath == null || prettierContainerPath.length == 0) { - process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path`); + process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`); process.exit(1); } fs.stat(prettierContainerPath, (err, stats) => { if (err) { - process.stderr.write(`Path '${prettierContainerPath}' does not exist.`); + process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`); process.exit(1); } if (!stats.isDirectory()) { - process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory.`); + process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`); process.exit(1); } }); @@ -27,10 +27,10 @@ const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); try { prettier = await loadPrettier(prettierPath); } catch (e) { - process.stderr.write(`Failed to load prettier: ${e}`); + process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully."); + process.stderr.write("Prettier loadded successfully\n"); process.stdin.resume(); handleBuffer(prettier); })() From f4667cbc3374463053f6e315bd90a535909a8ae0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 15:52:34 +0300 Subject: [PATCH 099/141] Resolve prettier config on server init --- crates/prettier/src/prettier.rs | 1 + crates/prettier/src/prettier_server.js | 20 +++++++++++++++----- crates/project/src/project.rs | 24 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5723cc67fc..01f1a055d3 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -222,6 +222,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + // TODO kb is not absolute now path, tab_width, }, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 001adca474..5896c295f6 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -21,18 +21,27 @@ fs.stat(prettierContainerPath, (err, stats) => { }); const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier'); +class Prettier { + constructor(path, prettier, config) { + this.path = path; + this.prettier = prettier; + this.config = config; + } +} (async () => { let prettier; + let config; try { prettier = await loadPrettier(prettierPath); + config = await prettier.resolveConfig(prettierPath) || {}; } catch (e) { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write("Prettier loadded successfully\n"); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); process.stdin.resume(); - handleBuffer(prettier); + handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { @@ -121,7 +130,7 @@ async function* readStdin() { } } -// ? +// TODO kb, more methods? // shutdown // error async function handleMessage(message, prettier) { @@ -140,10 +149,11 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.format(params.text, params.options); + const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { - prettier.clearConfigCache(); + prettier.prettier.clearConfigCache(); + prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {}; sendResponse({ id, result: null }); } else if (method === 'initialize') { sendResponse({ diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2afedecad6..a25fc2e178 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,7 @@ impl Project { .detach(); } - // TODO kb restart all formatters if settings change + // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -5987,6 +5987,7 @@ impl Project { this.update_local_worktree_buffers(&worktree, changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); this.update_local_worktree_settings(&worktree, changes, cx); + this.update_prettier_settings(&worktree, changes, cx); cx.emit(Event::WorktreeUpdatedEntries( worktree.read(cx).id(), changes.clone(), @@ -6366,6 +6367,27 @@ impl Project { .detach(); } + fn update_prettier_settings( + &self, + worktree: &ModelHandle, + changes: &[(Arc, ProjectEntryId, PathChange)], + cx: &mut ModelContext<'_, Project>, + ) { + let prettier_config_files = Prettier::CONFIG_FILE_NAMES + .iter() + .map(Path::new) + .collect::>(); + let prettier_config_changes = changes + .iter() + .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .collect::>(); + dbg!(prettier_config_changes); + dbg!(changes.len()); + // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ + // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. + // !! Ignore **/node_modules/** files in both checks above. + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; From b109075bf2e3eaa3aa5b11ea3b2e21ba8daf3827 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 16:40:45 +0300 Subject: [PATCH 100/141] Watch for prettier file changes --- crates/project/src/project.rs | 60 +++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a25fc2e178..bcf640d865 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6377,15 +6377,58 @@ impl Project { .iter() .map(Path::new) .collect::>(); - let prettier_config_changes = changes + + let prettier_config_file_changed = changes .iter() - .filter(|(path, _, _)| prettier_config_files.contains(path.as_ref())) + .filter(|(_, _, change)| !matches!(change, PathChange::Loaded)) + .filter(|(path, _, _)| { + !path + .components() + .any(|component| component.as_os_str().to_str() == Some("node_modules")) + }) + .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); + let current_worktree_id = worktree.read(cx).id(); + if let Some((config_path, _, _)) = prettier_config_file_changed { + log::info!( + "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" + ); + } + + let prettiers_to_reload = self + .prettier_instances + .iter() + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None + } + }) .collect::>(); - dbg!(prettier_config_changes); - dbg!(changes.len()); - // TODO kb: reset caches for all worktree-related prettiers (including the default one) on prettier config file _changes_ - // prepare node + prettier + plugins + prettier_server on files with Languages that have prettier formatter. Do not start it yet. - // !! Ignore **/node_modules/** files in both checks above. + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } + } + }) + .detach(); } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8305,6 +8348,7 @@ impl Project { return existing_prettier; } + log::info!("Found prettier at {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8321,6 +8365,8 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + if let Some(project) = weak_project.upgrade(&mut cx) { project.update(&mut cx, |project, cx| { let name = if prettier.is_default() { From 6ec3927dd3bdd0e0729990d9dd9fbdcf196b6743 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:40:12 +0300 Subject: [PATCH 101/141] Allow to configure default prettier --- Cargo.lock | 1 + assets/settings/default.json | 16 ++++++++++- crates/language/src/language_settings.rs | 8 ++++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 36 ++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 8 +++++- crates/project/src/project.rs | 3 +- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1795d3d748..4d9b3ee713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "collections", "fs", "futures 0.3.28", "gpui", diff --git a/assets/settings/default.json b/assets/settings/default.json index be47ac9c8c..1611d80e2f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -199,7 +199,11 @@ // "arguments": ["--stdin-filepath", "{buffer_path}"] // } // } - // TODO kb description + // 3. Format code using Zed's Prettier integration: + // "formatter": "prettier" + // 4. Default. Format files using Zed's Prettier integration (if applicable), + // or falling back to formatting via language server: + // "formatter": "auto" "formatter": "auto", // How to soft-wrap long lines of text. This setting can take // three values: @@ -430,6 +434,16 @@ "tab_size": 2 } }, + // Zed's Prettier integration settings. + // If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if + // project has no other Prettier installed. + "prettier": { + // Use regular Prettier json configuration: + // "trailingComma": "es5", + // "tabWidth": 4, + // "semi": false, + // "singleQuote": true + }, // LSP Specific settings. "lsp": { // Specify the LSP name as a key here. diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8778ba8d64..9cac5c523e 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -50,6 +50,7 @@ pub struct LanguageSettings { pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, + pub prettier: HashMap, pub enable_language_server: bool, pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { #[serde(default)] pub formatter: Option, #[serde(default)] + pub prettier: Option>, + #[serde(default)] pub enable_language_server: Option, #[serde(default)] pub show_copilot_suggestions: Option, @@ -155,9 +158,7 @@ pub enum Formatter { #[default] Auto, LanguageServer, - Prettier { - config: (), // Support some of the most important settings in the prettier-vscode extension. - }, + Prettier, External { command: Arc, arguments: Arc<[String]>, @@ -397,6 +398,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent src.preferred_line_length, ); merge(&mut settings.formatter, src.formatter.clone()); + merge(&mut settings.prettier, src.prettier.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); merge( &mut settings.remove_trailing_whitespace_on_save, diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index d10e8abdf9..129e928da2 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } fs = { path = "../fs" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 01f1a055d3..77baf6cbf3 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -1,8 +1,9 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use collections::HashMap; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -202,7 +203,6 @@ impl Prettier { let params = buffer.read_with(cx, |buffer, cx| { let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let language_settings = language_settings(buffer_language, buffer_file, cx); let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); @@ -217,14 +217,38 @@ impl Prettier { } }) }); - let tab_width = Some(language_settings.tab_size.get()); + + let prettier_options = if self.default { + let language_settings = language_settings(buffer_language, buffer_file, cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; + FormatParams { text: buffer.text(), options: FormatOptions { parser, // TODO kb is not absolute now path, - tab_width, + prettier_options, }, } }); @@ -318,13 +342,13 @@ struct FormatParams { options: FormatOptions, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { parser: Option, #[serde(rename = "filepath")] path: Option, - tab_width: Option, + prettier_options: Option>, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 5896c295f6..0a16a73b14 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -149,7 +149,13 @@ async function handleMessage(message, prettier) { if (params.options === undefined) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } - const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options }); + + const options = { + ...(params.options.prettierOptions || prettier.config), + parser: params.options.parser, + path: params.options.path + }; + const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { prettier.prettier.clearConfigCache(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bcf640d865..059256ee3f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,6 @@ impl Project { .detach(); } - // TODO kb restart all default formatters if Zed prettier settings change for (worktree, language, settings) in language_formatters_to_check { self.maybe_start_default_formatters(worktree, &language, &settings, cx); } @@ -8428,7 +8427,7 @@ impl Project { } fn maybe_start_default_formatters( - &mut self, + &self, worktree: Option, new_language: &Language, language_settings: &LanguageSettings, From afee29ad3f9df1eb79d43ed12a593e3449bc0c55 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 18:48:30 +0300 Subject: [PATCH 102/141] Do not clear cache for default prettiers --- crates/prettier/src/prettier_server.js | 3 ++ crates/project/src/project.rs | 72 +++++++++++++------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 0a16a73b14..dcc625b877 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -155,6 +155,9 @@ async function handleMessage(message, prettier) { parser: params.options.parser, path: params.options.path }; + // TODO kb always resolve prettier config for each file. + // need to understand if default prettier can be affected by other configs in the project + // (restart default prettiers on config changes too then) const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 059256ee3f..df504ad506 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6391,43 +6391,42 @@ impl Project { log::info!( "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); + let prettiers_to_reload = self + .prettier_instances + .iter() + .filter_map(|((worktree_id, prettier_path), prettier_task)| { + if worktree_id == &Some(current_worktree_id) { + Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) + } else { + None + } + }) + .collect::>(); + + cx.background() + .spawn(async move { + for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { + async move { + prettier_task.await? + .clear_cache() + .await + .with_context(|| { + format!( + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + ) + }) + .map_err(Arc::new) + } + })) + .await + { + if let Err(e) = task_result { + log::error!("Failed to clear cache for prettier: {e:#}"); + } + } + }) + .detach(); } - - let prettiers_to_reload = self - .prettier_instances - .iter() - .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { - Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) - } else { - None - } - }) - .collect::>(); - - cx.background() - .spawn(async move { - for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { - async move { - prettier_task.await? - .clear_cache() - .await - .with_context(|| { - format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" - ) - }) - .map_err(Arc::new) - } - })) - .await - { - if let Err(e) = task_result { - log::error!("Failed to clear cache for prettier: {e:#}"); - } - } - }) - .detach(); } pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { @@ -8410,6 +8409,7 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); + // TODO kb could there be a race with multiple default prettier instances added? cx.emit(Event::LanguageServerAdded(new_server_id)); }); } From 8a807102a668c57eb18c8dfbdeef467066f25685 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Sep 2023 23:49:03 +0300 Subject: [PATCH 103/141] Properly support prettier plugins --- Cargo.lock | 1 + crates/language/src/language.rs | 10 ++-- crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 72 ++++++++++++++++++++++---- crates/prettier/src/prettier_server.js | 1 + crates/project/src/project.rs | 30 ++++++----- crates/zed/src/languages/svelte.rs | 9 +++- crates/zed/src/languages/tailwind.rs | 9 +++- 8 files changed, 100 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d9b3ee713..0175755f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5528,6 +5528,7 @@ dependencies = [ "futures 0.3.28", "gpui", "language", + "log", "lsp", "node_runtime", "serde", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 31415a2dd6..c565d09c98 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -338,10 +338,6 @@ pub trait LspAdapter: 'static + Send + Sync { Default::default() } - // TODO kb enable this for - // markdown somehow? - // tailwind (needs a css plugin, there are 2 of them) - // svelte (needs a plugin) fn enabled_formatters(&self) -> Vec { Vec::new() } @@ -350,15 +346,15 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { - parser_name: &'static str, - plugin_names: Vec, + parser_name: Option<&'static str>, + plugin_names: Vec<&'static str>, }, } impl BundledFormatter { pub fn prettier(parser_name: &'static str) -> Self { Self::Prettier { - parser_name, + parser_name: Some(parser_name), plugin_names: Vec::new(), } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 129e928da2..cfac4c4757 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -15,6 +15,7 @@ lsp = { path = "../lsp" } node_runtime = { path = "../node_runtime"} util = { path = "../util" } +log.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 77baf6cbf3..3e21cad272 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use collections::HashMap; +use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; @@ -29,6 +29,7 @@ pub struct LocateStart { pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; +const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { // This was taken from the prettier-vscode extension. @@ -206,17 +207,64 @@ impl Prettier { let path = buffer_file .map(|file| file.full_path(cx)) .map(|path| path.to_path_buf()); - let parser = buffer_language.and_then(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .find_map(|formatter| match formatter { - BundledFormatter::Prettier { parser_name, .. } => { - Some(parser_name.to_string()) + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) + }) + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), } - }) - }); + parsers_with_plugins + }, + ); + + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + // TODO kb move the entire prettier server js file into *.mjs one instead? + let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); + let (parser, plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + if add_tailwind_back { + plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; let prettier_options = if self.default { let language_settings = language_settings(buffer_language, buffer_file, cx); @@ -246,6 +294,7 @@ impl Prettier { text: buffer.text(), options: FormatOptions { parser, + plugins, // TODO kb is not absolute now path, prettier_options, @@ -345,6 +394,7 @@ struct FormatParams { #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FormatOptions { + plugins: Vec, parser: Option, #[serde(rename = "filepath")] path: Option, diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index dcc625b877..780a9c3106 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -153,6 +153,7 @@ async function handleMessage(message, prettier) { const options = { ...(params.options.prettierOptions || prettier.config), parser: params.options.parser, + plugins: params.options.plugins, path: params.options.path }; // TODO kb always resolve prettier config for each file. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index df504ad506..f66ca039d7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -852,6 +852,7 @@ impl Project { } } let worktree = buffer_file + // TODO kb wrong usage (+ look around for another one like this) .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); language_formatters_to_check.push(( @@ -912,7 +913,7 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.maybe_start_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx); } // Start all the newly-enabled language servers. @@ -2669,7 +2670,7 @@ impl Project { .map(|f| f.worktree_id()) .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); - self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + self.install_default_formatters(worktree, &new_language, &settings, cx); if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); @@ -6395,7 +6396,7 @@ impl Project { .prettier_instances .iter() .filter_map(|((worktree_id, prettier_path), prettier_task)| { - if worktree_id == &Some(current_worktree_id) { + if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) { Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) } else { None @@ -6412,7 +6413,7 @@ impl Project { .await .with_context(|| { format!( - "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" + "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" ) }) .map_err(Arc::new) @@ -8410,6 +8411,7 @@ impl Project { .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier.server()))); // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8426,7 +8428,7 @@ impl Project { Some(task) } - fn maybe_start_default_formatters( + fn install_default_formatters( &self, worktree: Option, new_language: &Language, @@ -8458,14 +8460,10 @@ impl Project { }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); - if let Some(_already_running) = self + let already_running_prettier = self .prettier_instances .get(&(worktree, default_prettier_dir.to_path_buf())) - { - // TODO kb need to compare plugins, install missing and restart prettier - // TODO kb move the entire prettier init logic into prettier.rs - return; - } + .cloned(); let fs = Arc::clone(&self.fs); cx.background() @@ -8478,8 +8476,7 @@ impl Project { let packages_to_versions = future::try_join_all( prettier_plugins .iter() - .map(|s| s.as_str()) - .chain(Some("prettier")) + .chain(Some(&"prettier")) .map(|package_name| async { let returned_package_name = package_name.to_string(); let latest_version = node.npm_package_latest_version(package_name) @@ -8497,6 +8494,13 @@ impl Project { (package.as_str(), version.as_str()) }).collect::>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + + if !prettier_plugins.is_empty() { + if let Some(prettier) = already_running_prettier { + prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; + } + } + anyhow::Ok(()) }) .detach_and_log_err(cx); diff --git a/crates/zed/src/languages/svelte.rs b/crates/zed/src/languages/svelte.rs index 5e42d80e77..2089fe88b1 100644 --- a/crates/zed/src/languages/svelte.rs +++ b/crates/zed/src/languages/svelte.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; @@ -95,6 +95,13 @@ impl LspAdapter for SvelteLspAdapter { "provideFormatter": true })) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: Some("svelte"), + plugin_names: vec!["prettier-plugin-svelte"], + }] + } } async fn get_cached_server_binary( diff --git a/crates/zed/src/languages/tailwind.rs b/crates/zed/src/languages/tailwind.rs index cf07fa71c9..8e81f728dc 100644 --- a/crates/zed/src/languages/tailwind.rs +++ b/crates/zed/src/languages/tailwind.rs @@ -6,7 +6,7 @@ use futures::{ FutureExt, StreamExt, }; use gpui::AppContext; -use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::{json, Value}; @@ -127,6 +127,13 @@ impl LspAdapter for TailwindLspAdapter { .into_iter(), ) } + + fn enabled_formatters(&self) -> Vec { + vec![BundledFormatter::Prettier { + parser_name: None, + plugin_names: vec!["prettier-plugin-tailwindcss"], + }] + } } async fn get_cached_server_binary( From 658b58378ec8e86e52da83bc089ffe6f49af7574 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:04:49 +0300 Subject: [PATCH 104/141] Properly use WorktreeId --- crates/project/src/project.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f66ca039d7..90e7f2446b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -841,22 +841,18 @@ impl Project { for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - let buffer_file = buffer.file(); + let buffer_file = File::from_dyn(buffer.file()); let buffer_language = buffer.language(); - let settings = language_settings(buffer_language, buffer_file, cx); + let settings = language_settings(buffer_language, buffer.file(), cx); if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(buffer_file) { + if let Some(file) = buffer_file { language_servers_to_start .push((file.worktree.clone(), Arc::clone(language))); } } - let worktree = buffer_file - // TODO kb wrong usage (+ look around for another one like this) - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); language_formatters_to_check.push(( - worktree, + buffer_file.map(|f| f.worktree_id(cx)), Arc::clone(language), settings.clone(), )); @@ -2665,14 +2661,12 @@ impl Project { }); let buffer_file = buffer.read(cx).file().cloned(); - let worktree = buffer_file - .as_ref() - .map(|f| f.worktree_id()) - .map(WorktreeId::from_usize); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + let buffer_file = File::from_dyn(buffer_file.as_ref()); + let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); self.install_default_formatters(worktree, &new_language, &settings, cx); - if let Some(file) = File::from_dyn(buffer_file.as_ref()) { + if let Some(file) = buffer_file { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); From a9f80a603ca98470080589e9badf5abe275e9532 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Sep 2023 00:39:46 +0300 Subject: [PATCH 105/141] Resolve prettier config before every formatting --- crates/prettier/src/prettier.rs | 11 +++-------- crates/prettier/src/prettier_server.js | 12 ++++++++---- crates/project/src/project.rs | 11 ++++++++--- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 3e21cad272..c08098ba23 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -199,14 +199,11 @@ impl Prettier { pub async fn format( &self, buffer: &ModelHandle, + buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { let params = buffer.read_with(cx, |buffer, cx| { - let buffer_file = buffer.file(); let buffer_language = buffer.language(); - let path = buffer_file - .map(|file| file.full_path(cx)) - .map(|path| path.to_path_buf()); let parsers_with_plugins = buffer_language .into_iter() .flat_map(|language| { @@ -242,7 +239,6 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - // TODO kb move the entire prettier server js file into *.mjs one instead? let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); let (parser, plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { @@ -267,7 +263,7 @@ impl Prettier { }; let prettier_options = if self.default { - let language_settings = language_settings(buffer_language, buffer_file, cx); + let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { options.insert( @@ -295,8 +291,7 @@ impl Prettier { options: FormatOptions { parser, plugins, - // TODO kb is not absolute now - path, + path: buffer_path, prettier_options, }, } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 780a9c3106..7e8d1b67cb 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -150,15 +150,19 @@ async function handleMessage(message, prettier) { throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`); } + let resolvedConfig = {}; + if (params.options.filepath !== undefined) { + resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {}; + } + const options = { ...(params.options.prettierOptions || prettier.config), + ...resolvedConfig, parser: params.options.parser, plugins: params.options.plugins, - path: params.options.path + path: params.options.filepath }; - // TODO kb always resolve prettier config for each file. - // need to understand if default prettier can be affected by other configs in the project - // (restart default prettiers on config changes too then) + process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`); const formattedText = await prettier.prettier.format(params.text, options); sendResponse({ id, result: { text: formattedText } }); } else if (method === 'prettier/clear_cache') { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 90e7f2446b..9786afa71b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4130,9 +4130,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -4168,9 +4171,12 @@ impl Project { .await { Ok(prettier) => { + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) + }); format_operation = Some(FormatOperation::Prettier( prettier - .format(buffer, &cx) + .format(buffer, buffer_path, &cx) .await .context("formatting via prettier")?, )); @@ -8309,7 +8315,6 @@ impl Project { let task = cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - // TODO kb can we have a cache for this instead? let prettier_dir = match cx .background() .spawn(Prettier::locate( From 2d5741aef8eb2f7c0986a2aa39bec73131350ddd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 15:08:39 +0300 Subject: [PATCH 106/141] Better prettier format logging --- crates/prettier/src/prettier.rs | 1 + crates/project/src/project.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index c08098ba23..7871d78d21 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -285,6 +285,7 @@ impl Prettier { } else { None }; + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); FormatParams { text: buffer.text(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9786afa71b..53797b5bb9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8489,6 +8489,7 @@ impl Project { .await .context("fetching latest npm versions")?; + log::info!("Fetching default prettier and plugins: {packages_to_versions:?}"); let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { (package.as_str(), version.as_str()) }).collect::>(); From 6c1c7eaf75e666281af278e6365e71d59638b3a5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Sep 2023 22:43:57 +0300 Subject: [PATCH 107/141] Better detect Svelte plugins --- crates/prettier/src/prettier.rs | 39 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 7871d78d21..eb25ec20fc 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -239,8 +239,24 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs"); - let (parser, plugins) = match selected_parser_with_plugins { + let prettier_node_modules = self.prettier_dir.join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } + } + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { Some((parser, plugins)) => { // Tailwind plugin requires being added last // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins @@ -253,9 +269,9 @@ impl Prettier { } else { true } - }).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::>(); + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); if add_tailwind_back { - plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)); + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); } (Some(parser.to_string()), plugins) }, @@ -285,9 +301,18 @@ impl Prettier { } else { None }; + + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, + } + }).collect(); log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - FormatParams { + anyhow::Ok(FormatParams { text: buffer.text(), options: FormatOptions { parser, @@ -295,8 +320,8 @@ impl Prettier { path: buffer_path, prettier_options, }, - } - }); + }) + }).context("prettier params calculation")?; let response = self .server .request::(params) From faf1d38a6debe283ccd4245d03205c285c5e8676 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 01:53:28 +0300 Subject: [PATCH 108/141] Draft local and remote prettier separation --- Cargo.lock | 1 + crates/prettier/Cargo.toml | 1 + crates/prettier/src/prettier.rs | 58 +++- crates/prettier/src/prettier_server.js | 3 - crates/project/src/project.rs | 368 +++++++++++++++++-------- crates/rpc/proto/zed.proto | 29 +- crates/rpc/src/proto.rs | 12 +- 7 files changed, 332 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0175755f96..fd10a3c3b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5523,6 +5523,7 @@ name = "prettier" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "fs", "futures 0.3.28", diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index cfac4c4757..586341e66c 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" path = "src/prettier.rs" [dependencies] +client = { path = "../client" } collections = { path = "../collections"} language = { path = "../language" } gpui = { path = "../gpui" } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index eb25ec20fc..147262baed 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; +use client::Client; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; @@ -13,13 +14,24 @@ use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; -pub struct Prettier { +pub enum Prettier { + Local(Local), + Remote(Remote), +} + +pub struct Local { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } +pub struct Remote { + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -48,6 +60,14 @@ impl Prettier { ".editorconfig", ]; + pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + Self::Remote(Remote { + worktree_id, + prettier_dir, + client, + }) + } + pub async fn locate( starting_path: Option, fs: Arc, @@ -188,12 +208,12 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self { + Ok(Self::Local(Local { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), prettier_dir, - }) + })) } pub async fn format( @@ -239,7 +259,7 @@ impl Prettier { log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); } - let prettier_node_modules = self.prettier_dir.join("node_modules"); + let prettier_node_modules = self.prettier_dir().join("node_modules"); anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); let plugin_name_into_path = |plugin_name: &str| { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); @@ -278,7 +298,7 @@ impl Prettier { None => (None, Vec::new()), }; - let prettier_options = if self.default { + let prettier_options = if self.is_default() { let language_settings = language_settings(buffer_language, buffer.file(), cx); let mut options = language_settings.prettier.clone(); if !options.contains_key("tabWidth") { @@ -323,7 +343,8 @@ impl Prettier { }) }).context("prettier params calculation")?; let response = self - .server + .server() + .expect("TODO kb split into local and remote") .request::(params) .await .context("prettier format request")?; @@ -332,26 +353,39 @@ impl Prettier { } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server + self.server() + .expect("TODO kb split into local and remote") .request::(()) .await .context("prettier clear cache") } - pub fn server(&self) -> &Arc { - &self.server + pub fn server(&self) -> Option<&Arc> { + match self { + Prettier::Local(local) => Some(&local.server), + Prettier::Remote(_) => None, + } } pub fn is_default(&self) -> bool { - self.default + match self { + Prettier::Local(local) => local.default, + Prettier::Remote(_) => false, + } } pub fn prettier_dir(&self) -> &Path { - &self.prettier_dir + match self { + Prettier::Local(local) => &local.prettier_dir, + Prettier::Remote(remote) => &remote.prettier_dir, + } } pub fn worktree_id(&self) -> Option { - self.worktree_id + match self { + Prettier::Local(local) => local.worktree_id, + Prettier::Remote(remote) => remote.worktree_id, + } } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index 7e8d1b67cb..db3e26ef6d 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -130,9 +130,6 @@ async function* readStdin() { } } -// TODO kb, more methods? -// shutdown -// error async function handleMessage(message, prettier) { const { method, id, params } = message; if (method === undefined) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 53797b5bb9..3e85e70728 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -613,6 +613,8 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); + client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); + client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -4124,10 +4126,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -4165,10 +4165,8 @@ impl Project { if let Some(prettier_task) = this .update(&mut cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) - }) { - match prettier_task - .await - .await + }).await { + match prettier_task.await { Ok(prettier) => { let buffer_path = buffer.read_with(&cx, |buffer, cx| { @@ -8288,143 +8286,269 @@ impl Project { } } + async fn handle_prettier_instance_for_buffer( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { + let buffer = this + .opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) + })?; + + let prettier_path = match prettier_instance_for_buffer_task.await { + Some(prettier) => match prettier.await { + Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), + Err(e) => { + anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") + } + }, + None => None, + }; + Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) + } + + async fn handle_invoke_prettier( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let prettier = this + .read_with(&cx, |this, _| { + this.prettier_instances + .get(&( + envelope.payload.worktree_id.map(WorktreeId::from_proto), + PathBuf::from(&envelope.payload.buffer_path), + )) + .cloned() + }) + .with_context(|| { + format!( + "Missing prettier for worktree {:?} and path {}", + envelope.payload.worktree_id, envelope.payload.buffer_path, + ) + })? + .await; + let prettier = match prettier { + Ok(prettier) => prettier, + Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), + }; + + let buffer = this + .update(&mut cx, |this, cx| { + this.opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + }) + .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + + let buffer_path = buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }); + + let diff = prettier + .format(&buffer, buffer_path, &cx) + .await + .context("handle buffer formatting")?; + todo!("TODO kb serialize diff") + } + fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, cx: &mut ModelContext, - ) -> Option, Arc>>>>> { + ) -> Task, Arc>>>>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); - let buffer_language = buffer.language()?; + let Some(buffer_language) = buffer.language() else { + return Task::ready(None); + }; if !buffer_language .lsp_adapters() .iter() .flat_map(|adapter| adapter.enabled_formatters()) .any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) { - return None; + return Task::ready(None); } - let node = Arc::clone(self.node.as_ref()?); let buffer_file = File::from_dyn(buffer_file); let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let worktree_path = buffer_file .as_ref() - .map(|file| file.worktree.read(cx).abs_path()); + .and_then(|file| Some(file.worktree.read(cx).abs_path())); let worktree_id = buffer_file.map(|file| file.worktree_id(cx)); - - let task = cx.spawn(|this, mut cx| async move { - let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); - let prettier_dir = match cx - .background() - .spawn(Prettier::locate( - worktree_path - .zip(buffer_path) - .map(|(worktree_root_path, starting_path)| LocateStart { - worktree_root_path, - starting_path, - }), - fs, - )) - .await - { - Ok(path) => path, - Err(e) => { - return Task::Ready(Some(Result::Err(Arc::new( - e.context("determining prettier path for worktree {worktree_path:?}"), - )))) - .shared(); - } + if self.is_local() || worktree_id.is_none() || worktree_path.is_none() { + let Some(node) = self.node.as_ref().map(Arc::clone) else { + return Task::ready(None); }; - - if let Some(existing_prettier) = this.update(&mut cx, |project, _| { - project - .prettier_instances - .get(&(worktree_id, prettier_dir.clone())) - .cloned() - }) { - return existing_prettier; - } - - log::info!("Found prettier at {prettier_dir:?}, starting."); - let task_prettier_dir = prettier_dir.clone(); - let weak_project = this.downgrade(); - let new_server_id = - this.update(&mut cx, |this, _| this.languages.next_language_server_id()); - let new_prettier_task = cx - .spawn(|mut cx| async move { - let prettier = Prettier::start( - worktree_id.map(|id| id.to_usize()), - new_server_id, - task_prettier_dir, - node, - cx.clone(), - ) + let task = cx.spawn(|this, mut cx| async move { + let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); + let prettier_dir = match cx + .background() + .spawn(Prettier::locate( + worktree_path.zip(buffer_path).map( + |(worktree_root_path, starting_path)| LocateStart { + worktree_root_path, + starting_path, + }, + ), + fs, + )) .await - .context("prettier start") - .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); - - if let Some(project) = weak_project.upgrade(&mut cx) { - project.update(&mut cx, |project, cx| { - let name = if prettier.is_default() { - LanguageServerName(Arc::from("prettier (default)")) - } else { - let prettier_dir = prettier.prettier_dir(); - let worktree_path = prettier - .worktree_id() - .map(WorktreeId::from_usize) - .and_then(|id| project.worktree_for_id(id, cx)) - .map(|worktree| worktree.read(cx).abs_path()); - match worktree_path { - Some(worktree_path) => { - if worktree_path.as_ref() == prettier_dir { - LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - ))) - } else { - let dir_to_display = match prettier_dir - .strip_prefix(&worktree_path) - .ok() - { - Some(relative_path) => relative_path, - None => prettier_dir, - }; - LanguageServerName(Arc::from(format!( - "prettier ({})", - dir_to_display.display(), - ))) - } - } - None => LanguageServerName(Arc::from(format!( - "prettier ({})", - prettier_dir.display(), - ))), - } - }; - project - .supplementary_language_servers - .insert(new_server_id, (name, Arc::clone(prettier.server()))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - cx.emit(Event::LanguageServerAdded(new_server_id)); - }); + { + Ok(path) => path, + Err(e) => { + return Some( + Task::ready(Err(Arc::new(e.context( + "determining prettier path for worktree {worktree_path:?}", + )))) + .shared(), + ); } - anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) - }) - .shared(); - this.update(&mut cx, |project, _| { - project - .prettier_instances - .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + }; + + if let Some(existing_prettier) = this.update(&mut cx, |project, _| { + project + .prettier_instances + .get(&(worktree_id, prettier_dir.clone())) + .cloned() + }) { + return Some(existing_prettier); + } + + log::info!("Found prettier at {prettier_dir:?}, starting."); + let task_prettier_dir = prettier_dir.clone(); + let weak_project = this.downgrade(); + let new_server_id = + this.update(&mut cx, |this, _| this.languages.next_language_server_id()); + let new_prettier_task = cx + .spawn(|mut cx| async move { + let prettier = Prettier::start( + worktree_id.map(|id| id.to_usize()), + new_server_id, + task_prettier_dir, + node, + cx.clone(), + ) + .await + .context("prettier start") + .map_err(Arc::new)?; + log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + + if let Some((project, prettier_server)) = + weak_project.upgrade(&mut cx).zip(prettier.server()) + { + project.update(&mut cx, |project, cx| { + let name = if prettier.is_default() { + LanguageServerName(Arc::from("prettier (default)")) + } else { + let prettier_dir = prettier.prettier_dir(); + let worktree_path = prettier + .worktree_id() + .map(WorktreeId::from_usize) + .and_then(|id| project.worktree_for_id(id, cx)) + .map(|worktree| worktree.read(cx).abs_path()); + match worktree_path { + Some(worktree_path) => { + if worktree_path.as_ref() == prettier_dir { + LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + ))) + } else { + let dir_to_display = match prettier_dir + .strip_prefix(&worktree_path) + .ok() + { + Some(relative_path) => relative_path, + None => prettier_dir, + }; + LanguageServerName(Arc::from(format!( + "prettier ({})", + dir_to_display.display(), + ))) + } + } + None => LanguageServerName(Arc::from(format!( + "prettier ({})", + prettier_dir.display(), + ))), + } + }; + + project + .supplementary_language_servers + .insert(new_server_id, (name, Arc::clone(prettier_server))); + // TODO kb could there be a race with multiple default prettier instances added? + // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + cx.emit(Event::LanguageServerAdded(new_server_id)); + }); + } + Ok(Arc::new(prettier)).map_err(Arc::new) + }) + .shared(); + this.update(&mut cx, |project, _| { + project + .prettier_instances + .insert((worktree_id, prettier_dir), new_prettier_task.clone()); + }); + Some(new_prettier_task) }); - new_prettier_task - }); - Some(task) + task + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + let request = proto::PrettierInstanceForBuffer { + project_id, + buffer_id: buffer.remote_id(), + }; + let task = cx.spawn(|this, mut cx| async move { + match client.request(request).await { + Ok(response) => { + response + .prettier_path + .map(PathBuf::from) + .map(|prettier_path| { + let prettier_task = Task::ready( + Ok(Arc::new(Prettier::remote( + worktree_id.map(|id| id.to_usize()), + prettier_path.clone(), + client, + ))) + .map_err(Arc::new), + ) + .shared(); + this.update(&mut cx, |project, _| { + project.prettier_instances.insert( + (worktree_id, prettier_path), + prettier_task.clone(), + ); + }); + prettier_task + }) + } + Err(e) => { + log::error!("Prettier init remote request failed: {e:#}"); + None + } + } + }); + + task + } else { + Task::ready(Some( + Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), + )) + } } fn install_default_formatters( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 3501e70e6a..64fd8c620d 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,12 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // current max: 144 + MoveChannel move_channel = 142; + + PrettierInstanceForBuffer prettier_instance_for_buffer = 145; + PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; + InvokePrettierForBuffer invoke_prettier_for_buffer = 147; + InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 } } @@ -1557,3 +1562,25 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } + +message PrettierInstanceForBuffer { + uint64 project_id = 1; + uint64 buffer_id = 2; +} + +message PrettierInstanceForBufferResponse { + optional string prettier_path = 1; +} + +message InvokePrettierForBuffer { + uint64 project_id = 1; + string buffer_path = 2; + uint64 buffer_id = 3; + optional uint64 worktree_id = 4; + string method = 5; + optional string command_parameters = 6; +} + +message InvokePrettierForBufferResponse { + optional string diff = 1; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index f0d7937f6f..b51f1d8ae9 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,6 +273,10 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), + (PrettierInstanceForBuffer, Background), + (InvokePrettierForBuffer, Background), + (PrettierInstanceForBufferResponse, Background), + (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -349,7 +353,9 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack) + (LeaveChannelBuffer, Ack), + (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), + (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -400,7 +406,9 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase + UpdateDiffBase, + PrettierInstanceForBuffer, + InvokePrettierForBuffer, ); entity_messages!( From 2ec2036c2f739894cd80c2f26f7f6471a93906ea Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Sep 2023 18:32:55 +0300 Subject: [PATCH 109/141] Invoke remote Prettier commands --- crates/prettier/src/prettier.rs | 289 ++++++++++++++++++-------------- crates/project/src/project.rs | 9 +- crates/rpc/proto/zed.proto | 9 +- 3 files changed, 172 insertions(+), 135 deletions(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 147262baed..2df96224b6 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -9,6 +9,7 @@ use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; use language::{Buffer, BundledFormatter, Diff}; +use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; @@ -216,148 +217,176 @@ impl Prettier { })) } + pub async fn invoke( + &self, + buffer: &ModelHandle, + buffer_path: Option, + method: &str, + cx: &AsyncAppContext, + ) -> anyhow::Result> { + match method { + Format::METHOD => self + .format(buffer, buffer_path, cx) + .await + .context("invoke method") + .map(Some), + ClearCache::METHOD => { + self.clear_cache().await.context("invoke method")?; + Ok(None) + } + unknown => anyhow::bail!("Unknown method {unknown}"), + } + } + pub async fn format( &self, buffer: &ModelHandle, buffer_path: Option, cx: &AsyncAppContext, ) -> anyhow::Result { - let params = buffer.read_with(cx, |buffer, cx| { - let buffer_language = buffer.language(); - let parsers_with_plugins = buffer_language - .into_iter() - .flat_map(|language| { - language - .lsp_adapters() - .iter() - .flat_map(|adapter| adapter.enabled_formatters()) - .filter_map(|formatter| match formatter { - BundledFormatter::Prettier { - parser_name, - plugin_names, - } => Some((parser_name, plugin_names)), + match self { + Self::Local(local) => { + let params = buffer.read_with(cx, |buffer, cx| { + let buffer_language = buffer.language(); + let parsers_with_plugins = buffer_language + .into_iter() + .flat_map(|language| { + language + .lsp_adapters() + .iter() + .flat_map(|adapter| adapter.enabled_formatters()) + .filter_map(|formatter| match formatter { + BundledFormatter::Prettier { + parser_name, + plugin_names, + } => Some((parser_name, plugin_names)), + }) }) - }) - .fold( - HashMap::default(), - |mut parsers_with_plugins, (parser_name, plugins)| { - match parser_name { - Some(parser_name) => parsers_with_plugins - .entry(parser_name) - .or_insert_with(HashSet::default) - .extend(plugins), - None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { - existing_plugins.extend(plugins.iter()); - }), - } - parsers_with_plugins - }, - ); + .fold( + HashMap::default(), + |mut parsers_with_plugins, (parser_name, plugins)| { + match parser_name { + Some(parser_name) => parsers_with_plugins + .entry(parser_name) + .or_insert_with(HashSet::default) + .extend(plugins), + None => parsers_with_plugins.values_mut().for_each(|existing_plugins| { + existing_plugins.extend(plugins.iter()); + }), + } + parsers_with_plugins + }, + ); - let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); - if parsers_with_plugins.len() > 1 { - log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len()); + if parsers_with_plugins.len() > 1 { + log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}"); + } + + let prettier_node_modules = self.prettier_dir().join("node_modules"); + anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); + let plugin_name_into_path = |plugin_name: &str| { + let prettier_plugin_dir = prettier_node_modules.join(plugin_name); + for possible_plugin_path in [ + prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("index.mjs"), + prettier_plugin_dir.join("plugin.js"), + prettier_plugin_dir.join("index.js"), + prettier_plugin_dir, + ] { + if possible_plugin_path.is_file() { + return Some(possible_plugin_path); + } + } + None + }; + let (parser, located_plugins) = match selected_parser_with_plugins { + Some((parser, plugins)) => { + // Tailwind plugin requires being added last + // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins + let mut add_tailwind_back = false; + + let mut plugins = plugins.into_iter().filter(|&&plugin_name| { + if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { + add_tailwind_back = true; + false + } else { + true + } + }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); + if add_tailwind_back { + plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); + } + (Some(parser.to_string()), plugins) + }, + None => (None, Vec::new()), + }; + + let prettier_options = if self.is_default() { + let language_settings = language_settings(buffer_language, buffer.file(), cx); + let mut options = language_settings.prettier.clone(); + if !options.contains_key("tabWidth") { + options.insert( + "tabWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.tab_size.get(), + )), + ); + } + if !options.contains_key("printWidth") { + options.insert( + "printWidth".to_string(), + serde_json::Value::Number(serde_json::Number::from( + language_settings.preferred_line_length, + )), + ); + } + Some(options) + } else { + None + }; + + let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { + match located_plugin_path { + Some(path) => Some(path), + None => { + log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); + None}, + } + }).collect(); + log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); + + anyhow::Ok(FormatParams { + text: buffer.text(), + options: FormatOptions { + parser, + plugins, + path: buffer_path, + prettier_options, + }, + }) + }).context("prettier params calculation")?; + let response = local + .server + .request::(params) + .await + .context("prettier format request")?; + let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); + Ok(diff_task.await) } - - let prettier_node_modules = self.prettier_dir().join("node_modules"); - anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}"); - let plugin_name_into_path = |plugin_name: &str| { - let prettier_plugin_dir = prettier_node_modules.join(plugin_name); - for possible_plugin_path in [ - prettier_plugin_dir.join("dist").join("index.mjs"), - prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), - prettier_plugin_dir.join("index.js"), - prettier_plugin_dir, - ] { - if possible_plugin_path.is_file() { - return Some(possible_plugin_path); - } - } - None - }; - let (parser, located_plugins) = match selected_parser_with_plugins { - Some((parser, plugins)) => { - // Tailwind plugin requires being added last - // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins - let mut add_tailwind_back = false; - - let mut plugins = plugins.into_iter().filter(|&&plugin_name| { - if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME { - add_tailwind_back = true; - false - } else { - true - } - }).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::>(); - if add_tailwind_back { - plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME))); - } - (Some(parser.to_string()), plugins) - }, - None => (None, Vec::new()), - }; - - let prettier_options = if self.is_default() { - let language_settings = language_settings(buffer_language, buffer.file(), cx); - let mut options = language_settings.prettier.clone(); - if !options.contains_key("tabWidth") { - options.insert( - "tabWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.tab_size.get(), - )), - ); - } - if !options.contains_key("printWidth") { - options.insert( - "printWidth".to_string(), - serde_json::Value::Number(serde_json::Number::from( - language_settings.preferred_line_length, - )), - ); - } - Some(options) - } else { - None - }; - - let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| { - match located_plugin_path { - Some(path) => Some(path), - None => { - log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}"); - None}, - } - }).collect(); - log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx))); - - anyhow::Ok(FormatParams { - text: buffer.text(), - options: FormatOptions { - parser, - plugins, - path: buffer_path, - prettier_options, - }, - }) - }).context("prettier params calculation")?; - let response = self - .server() - .expect("TODO kb split into local and remote") - .request::(params) - .await - .context("prettier format request")?; - let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); - Ok(diff_task.await) + Self::Remote(remote) => todo!("TODO kb"), + } } pub async fn clear_cache(&self) -> anyhow::Result<()> { - self.server() - .expect("TODO kb split into local and remote") - .request::(()) - .await - .context("prettier clear cache") + match self { + Self::Local(local) => local + .server + .request::(()) + .await + .context("prettier clear cache"), + Self::Remote(remote) => todo!("TODO kb"), + } } pub fn server(&self) -> Option<&Arc> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3e85e70728..f3a92ad00f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8353,10 +8353,13 @@ impl Project { }); let diff = prettier - .format(&buffer, buffer_path, &cx) + .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) .await - .context("handle buffer formatting")?; - todo!("TODO kb serialize diff") + .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; + + Ok(proto::InvokePrettierForBufferResponse { + diff: todo!("TODO kb serialize diff"), + }) } fn prettier_instance_for_buffer( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 64fd8c620d..0622fcef90 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1578,9 +1578,14 @@ message InvokePrettierForBuffer { uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; - optional string command_parameters = 6; } message InvokePrettierForBufferResponse { - optional string diff = 1; + optional Diff diff = 1; +} + +message Diff { + VectorClockEntry version = 1; + string line_ending = 2; + string edits = 3; } From b5705e079fd90276916b7e553ebecfa9ebe81158 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 27 Sep 2023 16:38:42 +0200 Subject: [PATCH 110/141] Draft remote prettier formatting --- crates/language/src/buffer.rs | 4 +-- crates/language/src/proto.rs | 41 ++++++++++++++++++++++- crates/prettier/src/prettier.rs | 58 ++++++++++++++++++++++++++++----- crates/project/src/project.rs | 35 +++++++++++--------- crates/rpc/proto/zed.proto | 15 ++++++--- 5 files changed, 121 insertions(+), 32 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 207c41e7cd..eccfb0c059 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - line_ending: LineEnding, - edits: Vec<(Range, Arc)>, + pub(crate) line_ending: LineEnding, + pub(crate) edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index c4abe39d47..5da1d5713f 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Language, + Diff, Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,3 +587,42 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { + proto::Diff { + version: serialize_version(&diff.base_version), + line_ending: serialize_line_ending(diff.line_ending) as i32, + edits: diff + .edits + .into_iter() + .map(|(range, edit)| proto::DiffEdit { + range: Some(proto::Range { + start: range.start as u64, + end: range.end as u64, + }), + edit: edit.to_string(), + }) + .collect(), + } +} + +pub fn deserialize_diff(diff: proto::Diff) -> Diff { + Diff { + base_version: deserialize_version(&diff.version), + line_ending: deserialize_line_ending( + rpc::proto::LineEnding::from_i32(diff.line_ending) + .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), + ), + edits: diff + .edits + .into_iter() + .map(|edit| { + let range = edit.range.expect("incorrect edit without a range"); + ( + range.start as usize..range.end as usize, + Arc::from(edit.edit.as_str()), + ) + }) + .collect(), + } +} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 2df96224b6..5647123c75 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,11 +3,12 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::Client; +use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; +use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; @@ -28,6 +29,7 @@ pub struct Local { } pub struct Remote { + project_id: u64, worktree_id: Option, prettier_dir: PathBuf, client: Arc, @@ -61,8 +63,14 @@ impl Prettier { ".editorconfig", ]; - pub fn remote(worktree_id: Option, prettier_dir: PathBuf, client: Arc) -> Self { + pub fn remote( + project_id: u64, + worktree_id: Option, + prettier_dir: PathBuf, + client: Arc, + ) -> Self { Self::Remote(Remote { + project_id, worktree_id, prettier_dir, client, @@ -80,7 +88,7 @@ impl Prettier { .components() .into_iter() .take_while(|path_component| { - path_component.as_os_str().to_str() != Some("node_modules") + path_component.as_os_str().to_string_lossy() != "node_modules" }) .collect::(); @@ -137,7 +145,7 @@ impl Prettier { for path_component in file_to_format.components().into_iter() { current_path = current_path.join(path_component); paths_to_check.push_front(current_path.clone()); - if path_component.as_os_str().to_str() == Some("node_modules") { + if path_component.as_os_str().to_string_lossy() == "node_modules" { break; } } @@ -219,14 +227,18 @@ impl Prettier { pub async fn invoke( &self, - buffer: &ModelHandle, + buffer: Option<&ModelHandle>, buffer_path: Option, method: &str, cx: &AsyncAppContext, ) -> anyhow::Result> { match method { Format::METHOD => self - .format(buffer, buffer_path, cx) + .format( + buffer.expect("missing buffer for format invocation"), + buffer_path, + cx, + ) .await .context("invoke method") .map(Some), @@ -374,7 +386,21 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => buffer + .read_with(cx, |buffer, _| { + remote.client.request(proto::InvokePrettierForBuffer { + buffer_id: Some(buffer.remote_id()), + worktree_id: self.worktree_id().map(|id| id as u64), + method: Format::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + }) + .await + .context("prettier diff invoke")? + .diff + .map(deserialize_diff) + .context("missing diff after prettier diff invocation"), } } @@ -385,7 +411,23 @@ impl Prettier { .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => todo!("TODO kb"), + Self::Remote(remote) => remote + .client + .request(proto::InvokePrettierForBuffer { + buffer_id: None, + worktree_id: self.worktree_id().map(|id| id as u64), + method: ClearCache::METHOD.to_string(), + project_id: remote.project_id, + prettier_path: remote.prettier_dir.to_string_lossy().to_string(), + }) + .await + .map(|response| { + debug_assert!( + response.diff.is_none(), + "Cleare cache invocation returned diff data" + ) + }) + .context("prettier invoke clear cache"), } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f3a92ad00f..9a1d78f083 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_version, split_operations, + serialize_anchor, serialize_diff, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -6382,7 +6382,7 @@ impl Project { .filter(|(path, _, _)| { !path .components() - .any(|component| component.as_os_str().to_str() == Some("node_modules")) + .any(|component| component.as_os_str().to_string_lossy() == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let current_worktree_id = worktree.read(cx).id(); @@ -8324,14 +8324,14 @@ impl Project { this.prettier_instances .get(&( envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.buffer_path), + PathBuf::from(&envelope.payload.prettier_path), )) .cloned() }) .with_context(|| { format!( - "Missing prettier for worktree {:?} and path {}", - envelope.payload.worktree_id, envelope.payload.buffer_path, + "Missing prettier for worktree {:?} and path {:?}", + envelope.payload.worktree_id, envelope.payload.prettier_path, ) })? .await; @@ -8340,25 +8340,27 @@ impl Project { Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), }; - let buffer = this - .update(&mut cx, |this, cx| { - this.opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - }) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; + let buffer = this.update(&mut cx, |this, cx| { + envelope + .payload + .buffer_id + .and_then(|id| this.opened_buffers.get(&id)) + .and_then(|buffer| buffer.upgrade(cx)) + }); - let buffer_path = buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + let buffer_path = buffer.as_ref().and_then(|buffer| { + buffer.read_with(&cx, |buffer, cx| { + File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) + }) }); let diff = prettier - .invoke(&buffer, buffer_path, &envelope.payload.method, &cx) + .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) .await .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; Ok(proto::InvokePrettierForBufferResponse { - diff: todo!("TODO kb serialize diff"), + diff: diff.map(serialize_diff), }) } @@ -8523,6 +8525,7 @@ impl Project { .map(|prettier_path| { let prettier_task = Task::ready( Ok(Arc::new(Prettier::remote( + project_id, worktree_id.map(|id| id.to_usize()), prettier_path.clone(), client, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 0622fcef90..cbd334e334 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - string buffer_path = 2; - uint64 buffer_id = 3; + optional uint64 buffer_id = 3; + string prettier_path = 2; optional uint64 worktree_id = 4; string method = 5; } @@ -1585,7 +1585,12 @@ message InvokePrettierForBufferResponse { } message Diff { - VectorClockEntry version = 1; - string line_ending = 2; - string edits = 3; + repeated VectorClockEntry version = 1; + LineEnding line_ending = 2; + repeated DiffEdit edits = 3; +} + +message DiffEdit { + Range range = 1; + string edit = 2; } From 9bf22c56cdb1adad44ba82b7fcd4b9b04c7451e2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 9 Oct 2023 15:44:48 +0300 Subject: [PATCH 111/141] Rebase fixes --- crates/collab/src/tests/test_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ccd48a0a1b..7397489b34 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -569,9 +569,9 @@ impl TestClient { cx.update(|cx| { Project::local( self.client().clone(), + self.app_state.node_runtime.clone(), self.app_state.user_store.clone(), self.app_state.languages.clone(), - self.app_state.node_runtime.clone(), self.app_state.fs.clone(), cx, ) From 986a516bf1237b343eb883c860019a692fb9a042 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 10 Oct 2023 17:16:43 +0300 Subject: [PATCH 112/141] Small style fixes --- crates/language/src/language.rs | 6 ++++++ crates/prettier/src/prettier.rs | 1 - crates/project/src/project.rs | 1 + crates/rpc/proto/zed.proto | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c565d09c98..73906250f3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -346,6 +346,12 @@ pub trait LspAdapter: 'static + Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] pub enum BundledFormatter { Prettier { + // See https://prettier.io/docs/en/options.html#parser for a list of valid values. + // Usually, every language has a single parser (standard or plugin-provided), hence `Some("parser_name")` can be used. + // There can not be multiple parsers for a single language, in case of a conflict, we would attempt to select the one with most plugins. + // + // But exceptions like Tailwind CSS exist, which uses standard parsers for CSS/JS/HTML/etc. but require an extra plugin to be installed. + // For those cases, `None` will install the plugin but apply other, regular parser defined for the language, and this would not be a conflict. parser_name: Option<&'static str>, plugin_names: Vec<&'static str>, }, diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 5647123c75..07a7cbd73a 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -47,7 +47,6 @@ const PRETTIER_PACKAGE_NAME: &str = "prettier"; const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; impl Prettier { - // This was taken from the prettier-vscode extension. pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ ".prettierrc", ".prettierrc.json", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9a1d78f083..9dc3ff8758 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -8496,6 +8496,7 @@ impl Project { .insert(new_server_id, (name, Arc::clone(prettier_server))); // TODO kb could there be a race with multiple default prettier instances added? // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) + // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index cbd334e334..aae207f41c 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse { message InvokePrettierForBuffer { uint64 project_id = 1; - optional uint64 buffer_id = 3; string prettier_path = 2; + optional uint64 buffer_id = 3; optional uint64 worktree_id = 4; string method = 5; } From 4a88a9e253d3cafc6dd54fc9c873a9098c2ec730 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 14:46:59 +0300 Subject: [PATCH 113/141] Initialize prettier right after the buffer gets it language --- crates/project/src/project.rs | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9dc3ff8758..ebc2aaaf60 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -911,7 +911,8 @@ impl Project { } for (worktree, language, settings) in language_formatters_to_check { - self.install_default_formatters(worktree, &language, &settings, cx); + self.install_default_formatters(worktree, &language, &settings, cx) + .detach_and_log_err(cx); } // Start all the newly-enabled language servers. @@ -2666,7 +2667,20 @@ impl Project { let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); - self.install_default_formatters(worktree, &new_language, &settings, cx); + + let task_buffer = buffer.clone(); + let prettier_installation_task = + self.install_default_formatters(worktree, &new_language, &settings, cx); + cx.spawn(|project, mut cx| async move { + prettier_installation_task.await?; + let _ = project + .update(&mut cx, |project, cx| { + project.prettier_instance_for_buffer(&task_buffer, cx) + }) + .await; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); if let Some(file) = buffer_file { let worktree = file.worktree.clone(); @@ -8393,7 +8407,7 @@ impl Project { let Some(node) = self.node.as_ref().map(Arc::clone) else { return Task::ready(None); }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); let prettier_dir = match cx .background() @@ -8494,9 +8508,6 @@ impl Project { project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier_server))); - // TODO kb could there be a race with multiple default prettier instances added? - // also, clean up prettiers for dropped workspaces (e.g. external files that got closed) - // also, is there a way to speed up initial prettier startup? now it takes a 1s or so on the first formatting attempt. cx.emit(Event::LanguageServerAdded(new_server_id)); }); } @@ -8509,15 +8520,14 @@ impl Project { .insert((worktree_id, prettier_dir), new_prettier_task.clone()); }); Some(new_prettier_task) - }); - task + }) } else if let Some(project_id) = self.remote_id() { let client = self.client.clone(); let request = proto::PrettierInstanceForBuffer { project_id, buffer_id: buffer.remote_id(), }; - let task = cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { match client.request(request).await { Ok(response) => { response @@ -8548,9 +8558,7 @@ impl Project { None } } - }); - - task + }) } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), @@ -8564,13 +8572,13 @@ impl Project { new_language: &Language, language_settings: &LanguageSettings, cx: &mut ModelContext, - ) { + ) -> Task> { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => {} - Formatter::LanguageServer | Formatter::External { .. } => return, + Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())), }; let Some(node) = self.node.as_ref().cloned() else { - return; + return Task::ready(Ok(())); }; let mut prettier_plugins = None; @@ -8586,7 +8594,7 @@ impl Project { } } let Some(prettier_plugins) = prettier_plugins else { - return; + return Task::ready(Ok(())); }; let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); @@ -8634,7 +8642,6 @@ impl Project { anyhow::Ok(()) }) - .detach_and_log_err(cx); } } From 2d6725a41a4f05e65c788ad200881a5dbce996bf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Oct 2023 09:45:57 -0600 Subject: [PATCH 114/141] Make collaboration warning more useful --- crates/collab_ui/src/collab_titlebar_item.rs | 49 +++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index d85aca164a..211ee863e8 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -2,6 +2,7 @@ use crate::{ contact_notification::ContactNotification, face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute, ToggleScreenSharing, }; +use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ContactEventKind, SignIn, SignOut, User, UserStore}; use clock::ReplicaId; @@ -1177,22 +1178,38 @@ impl CollabTitlebarItem { .with_style(theme.titlebar.offline_icon.container) .into_any(), ), - client::Status::UpgradeRequired => Some( - MouseEventHandler::new::(0, cx, |_, _| { - Label::new( - "Please update Zed to collaborate", - theme.titlebar.outdated_warning.text.clone(), - ) - .contained() - .with_style(theme.titlebar.outdated_warning.container) - .aligned() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, _, cx| { - auto_update::check(&Default::default(), cx); - }) - .into_any(), - ), + client::Status::UpgradeRequired => { + let auto_updater = auto_update::AutoUpdater::get(cx); + let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { + Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", + Some(AutoUpdateStatus::Installing) + | Some(AutoUpdateStatus::Downloading) + | Some(AutoUpdateStatus::Checking) => "Updating...", + Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { + "Please update Zed to Collaborate" + } + }; + + Some( + MouseEventHandler::new::(0, cx, |_, _| { + Label::new(label, theme.titlebar.outdated_warning.text.clone()) + .contained() + .with_style(theme.titlebar.outdated_warning.container) + .aligned() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, _, cx| { + if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { + if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { + workspace::restart(&Default::default(), cx); + return; + } + } + auto_update::check(&Default::default(), cx); + }) + .into_any(), + ) + } _ => None, } } From e50f4c0ee5f3e32f166e6cb16e02042413088074 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 11 Oct 2023 19:08:35 +0300 Subject: [PATCH 115/141] Add prettier tests infrastructure --- crates/editor/src/editor_tests.rs | 66 ++++++++++++++++++++++++- crates/language/src/language.rs | 6 +++ crates/node_runtime/src/node_runtime.rs | 25 +++++++--- crates/prettier/Cargo.toml | 3 ++ crates/prettier/src/prettier.rs | 54 ++++++++++++++++---- crates/project/Cargo.toml | 2 + 6 files changed, 139 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dc723c7012..ddae186679 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -19,8 +19,8 @@ use gpui::{ use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, - BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, - Override, Point, + BracketPairConfig, BundledFormatter, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, + LanguageRegistry, Override, Point, }; use parking_lot::Mutex; use project::project_settings::{LspSettings, ProjectSettings}; @@ -7815,6 +7815,68 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: }); } +#[gpui::test] +async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Prettier) + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + + let _ = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec!["test_plugin"], + }], + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_file("/file.rs", Default::default()).await; + + // TODO kb have to specify some test node runtime + let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let buffer = project + .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) + .await + .unwrap(); + + let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); + let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one, two\nthree\n" + ); + + editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project, FormatTrigger::Manual, cx) + }); + cx.foreground().advance_clock(super::FORMAT_TIMEOUT); + cx.foreground().start_waiting(); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "one\ntwo\nthree\n" + ); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 73906250f3..bd389652a0 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -498,6 +498,7 @@ pub struct FakeLspAdapter { pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, pub disk_based_diagnostics_sources: Vec, + pub enabled_formatters: Vec, } #[derive(Clone, Debug, Default)] @@ -1760,6 +1761,7 @@ impl Default for FakeLspAdapter { disk_based_diagnostics_progress_token: None, initialization_options: None, disk_based_diagnostics_sources: Vec::new(), + enabled_formatters: Vec::new(), } } } @@ -1816,6 +1818,10 @@ impl LspAdapter for Arc { async fn initialization_options(&self) -> Option { self.initialization_options.clone() } + + fn enabled_formatters(&self) -> Vec { + self.enabled_formatters.clone() + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 820a8b6f81..8bfb26cdad 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -230,19 +230,32 @@ impl FakeNodeRuntime { #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { - async fn binary_path(&self) -> Result { - unreachable!() + async fn binary_path(&self) -> anyhow::Result { + // TODO kb move away into a separate type + a Project's setter (for test code) + Ok(PathBuf::from("prettier_fake_node")) } async fn run_npm_subcommand(&self, _: Option<&Path>, _: &str, _: &[&str]) -> Result { unreachable!() } - async fn npm_package_latest_version(&self, _: &str) -> Result { - unreachable!() + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if name == "prettier" { + Ok("0.0.1".to_string()) + } else { + unreachable!() + } } - async fn npm_install_packages(&self, _: &Path, _: &[(&str, &str)]) -> Result<()> { - unreachable!() + async fn npm_install_packages( + &self, + _: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if packages == [("prettier", "0.0.1")] { + Ok(()) + } else { + unreachable!() + } } } diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 586341e66c..764bf0f07f 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" [lib] path = "src/prettier.rs" +[features] +test-support = [] + [dependencies] client = { path = "../client" } collections = { path = "../collections"} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 07a7cbd73a..9dfc56f38c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -11,7 +11,7 @@ use language::language_settings::language_settings; use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; use lsp::request::Request; -use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; @@ -19,6 +19,8 @@ use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { Local(Local), Remote(Remote), + #[cfg(any(test, feature = "test-support"))] + Test(TestPrettier), } pub struct Local { @@ -35,6 +37,13 @@ pub struct Remote { client: Arc, } +#[cfg(any(test, feature = "test-support"))] +pub struct TestPrettier { + worktree_id: Option, + prettier_dir: PathBuf, + default: bool, +} + #[derive(Debug)] pub struct LocateStart { pub worktree_root_path: Arc, @@ -180,6 +189,22 @@ impl Prettier { } } + #[cfg(any(test, feature = "test-support"))] + pub async fn start( + worktree_id: Option, + _: LanguageServerId, + prettier_dir: PathBuf, + _: Arc, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + })) + } + + #[cfg(not(any(test, feature = "test-support")))] pub async fn start( worktree_id: Option, server_id: LanguageServerId, @@ -400,6 +425,12 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + Self::Test(_) => Ok(buffer + .read_with(cx, |buffer, cx| { + let formatted_text = buffer.text() + "\nformatted by test prettier"; + buffer.diff(formatted_text, cx) + }) + .await), } } @@ -427,34 +458,39 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + Self::Test(_) => Ok(()), } } pub fn server(&self) -> Option<&Arc> { match self { - Prettier::Local(local) => Some(&local.server), - Prettier::Remote(_) => None, + Self::Local(local) => Some(&local.server), + Self::Remote(_) => None, + Self::Test(_) => None, } } pub fn is_default(&self) -> bool { match self { - Prettier::Local(local) => local.default, - Prettier::Remote(_) => false, + Self::Local(local) => local.default, + Self::Remote(_) => false, + Self::Test(test_prettier) => test_prettier.default, } } pub fn prettier_dir(&self) -> &Path { match self { - Prettier::Local(local) => &local.prettier_dir, - Prettier::Remote(remote) => &remote.prettier_dir, + Self::Local(local) => &local.prettier_dir, + Self::Remote(remote) => &remote.prettier_dir, + Self::Test(test_prettier) => &test_prettier.prettier_dir, } } pub fn worktree_id(&self) -> Option { match self { - Prettier::Local(local) => local.worktree_id, - Prettier::Remote(remote) => remote.worktree_id, + Self::Local(local) => local.worktree_id, + Self::Remote(remote) => remote.worktree_id, + Self::Test(test_prettier) => test_prettier.worktree_id, } } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 9f505c3fd2..cfa623d534 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -15,6 +15,7 @@ test-support = [ "language/test-support", "settings/test-support", "text/test-support", + "prettier/test-support", ] [dependencies] @@ -75,6 +76,7 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } +prettier = { path = "../prettier", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } git2.workspace = true From bdf1731db397516c1fde02dd1f745edb97e92179 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 12:40:57 -0400 Subject: [PATCH 116/141] v0.109.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd36221de0..faf493be09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10081,7 +10081,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.108.0" +version = "0.109.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 7eb14559be..4174f7d6d5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.108.0" +version = "0.109.0" publish = false [lib] From 0cec0c1c1d82c5b2c5bc0fe5b7afaf9f9073337f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 13:41:58 -0400 Subject: [PATCH 117/141] Fixup layout --- crates/editor/src/editor.rs | 19 ++++++++++++++++--- crates/theme/src/theme.rs | 4 +++- styles/src/style_tree/editor.ts | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 06482dbbc6..3fc47f48e9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1240,6 +1240,9 @@ impl CompletionsMenu { ) .map(|task| task.detach()); }) + .constrained() + .with_min_width(style.autocomplete.completion_min_width) + .with_max_width(style.autocomplete.completion_max_width) .into_any(), ); } @@ -1250,7 +1253,7 @@ impl CompletionsMenu { enum MultiLineDocumentation {} Flex::row() - .with_child(list) + .with_child(list.flex(1., false)) .with_children({ let mat = &self.matches[selected_item]; let completions = self.completions.read(); @@ -1263,7 +1266,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child( Text::new(text.clone(), style.text.clone()).with_soft_wrap(true), - ), + ) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), Some(Documentation::MultiLineMarkdown(parsed)) => Some( @@ -1271,7 +1279,12 @@ impl CompletionsMenu { .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( parsed, &style, cx, - )), + )) + .contained() + .with_style(style.autocomplete.alongside_docs_container) + .constrained() + .with_max_width(style.autocomplete.alongside_docs_max_width) + .flex(1., false), ), _ => None, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9f7530ec18..f335444b58 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -867,10 +867,12 @@ pub struct AutocompleteStyle { pub selected_item: ContainerStyle, pub hovered_item: ContainerStyle, pub match_highlight: HighlightStyle, + pub completion_min_width: f32, + pub completion_max_width: f32, pub inline_docs_container: ContainerStyle, pub inline_docs_color: Color, pub inline_docs_size_percent: f32, - pub alongside_docs_width: f32, + pub alongside_docs_max_width: f32, pub alongside_docs_container: ContainerStyle, } diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index e7717583a8..27a6eaf195 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -206,10 +206,12 @@ export default function editor(): any { match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, + completion_min_width: 300, + completion_max_width: 700, inline_docs_container: { padding: { left: 40 } }, inline_docs_color: text(theme.middle, "sans", "disabled", {}).color, inline_docs_size_percent: 0.75, - alongside_docs_width: 700, + alongside_docs_max_width: 700, alongside_docs_container: { padding: autocomplete_item.padding } }, diagnostic_header: { From d6fa06b3bee177ff02bc7c987a8c9b1c964eacfd Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 11 Oct 2023 13:51:01 -0400 Subject: [PATCH 118/141] collab 0.24.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faf493be09..72ee771f5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1466,7 +1466,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.23.3" +version = "0.24.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 6177c23620..8fd1cd4380 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.23.3" +version = "0.24.0" publish = false [[bin]] From a09ee3a41b2a95f2dc016b588970e9b97537e7b8 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 11 Oct 2023 14:39:34 -0400 Subject: [PATCH 119/141] Fire markdown link on mouse down Previously any amount of mouse movement would disqualify the mouse down and up from being a click, being a drag instead, which is a long standing UX issue. We can get away with just firing on mouse down here for now --- crates/editor/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3fc47f48e9..9c1e0b3c18 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -154,7 +154,7 @@ pub fn render_parsed_markdown( }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_click::(MouseButton::Left, move |_, _, cx| { + .on_down::(MouseButton::Left, move |_, _, cx| { cx.platform().open_url(&url) }), ); From a528c6c68610ab075ace8aad4bb22a1b6cf46d94 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:31:30 +0300 Subject: [PATCH 120/141] Prettier server style fixes --- crates/node_runtime/src/node_runtime.rs | 4 ++-- crates/prettier/src/prettier.rs | 21 ++++++++++++++++----- crates/prettier/src/prettier_server.js | 23 +++++++++++++---------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 8bfb26cdad..125318dd67 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -243,7 +243,7 @@ impl NodeRuntime for FakeNodeRuntime { if name == "prettier" { Ok("0.0.1".to_string()) } else { - unreachable!() + unreachable!("Unexpected package name: {name}") } } @@ -255,7 +255,7 @@ impl NodeRuntime for FakeNodeRuntime { if packages == [("prettier", "0.0.1")] { Ok(()) } else { - unreachable!() + unreachable!("Unexpected packages to install: {packages:?}") } } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 9dfc56f38c..6c1d0665e2 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -197,11 +197,14 @@ impl Prettier { _: Arc, _: AsyncAppContext, ) -> anyhow::Result { - Ok(Self::Test(TestPrettier { - worktree_id, - default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), - prettier_dir, - })) + Ok( + #[cfg(any(test, feature = "test-support"))] + Self::Test(TestPrettier { + worktree_id, + default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), + prettier_dir, + }), + ) } #[cfg(not(any(test, feature = "test-support")))] @@ -212,6 +215,8 @@ impl Prettier { node: Arc, cx: AsyncAppContext, ) -> anyhow::Result { + use lsp::LanguageServerBinary; + let backgroud = cx.background(); anyhow::ensure!( prettier_dir.is_dir(), @@ -425,6 +430,7 @@ impl Prettier { .diff .map(deserialize_diff) .context("missing diff after prettier diff invocation"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { let formatted_text = buffer.text() + "\nformatted by test prettier"; @@ -458,6 +464,7 @@ impl Prettier { ) }) .context("prettier invoke clear cache"), + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } } @@ -466,6 +473,7 @@ impl Prettier { match self { Self::Local(local) => Some(&local.server), Self::Remote(_) => None, + #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } } @@ -474,6 +482,7 @@ impl Prettier { match self { Self::Local(local) => local.default, Self::Remote(_) => false, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } } @@ -482,6 +491,7 @@ impl Prettier { match self { Self::Local(local) => &local.prettier_dir, Self::Remote(remote) => &remote.prettier_dir, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } } @@ -490,6 +500,7 @@ impl Prettier { match self { Self::Local(local) => local.worktree_id, Self::Remote(remote) => remote.worktree_id, + #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } } diff --git a/crates/prettier/src/prettier_server.js b/crates/prettier/src/prettier_server.js index db3e26ef6d..a56c220f20 100644 --- a/crates/prettier/src/prettier_server.js +++ b/crates/prettier/src/prettier_server.js @@ -39,13 +39,13 @@ class Prettier { process.stderr.write(`Failed to load prettier: ${e}\n`); process.exit(1); } - process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${config}\n`); + process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`); process.stdin.resume(); handleBuffer(new Prettier(prettierPath, prettier, config)); })() async function handleBuffer(prettier) { - for await (let messageText of readStdin()) { + for await (const messageText of readStdin()) { let message; try { message = JSON.parse(messageText); @@ -53,6 +53,7 @@ async function handleBuffer(prettier) { sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`)); continue; } + // allow concurrent request handling by not `await`ing the message handling promise (async function) handleMessage(message, prettier).catch(e => { sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) }); }); @@ -96,16 +97,18 @@ async function* readStdin() { await once(process.stdin, 'readable'); } const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii'); - const contentLengthHeader = headers.split(headerSeparator).map(header => header.split(':')) + const contentLengthHeader = headers.split(headerSeparator) + .map(header => header.split(':')) .filter(header => header[2] === undefined) .filter(header => (header[1] || '').length > 0) - .find(header => header[0].trim() === contentLengthHeaderName); - if (contentLengthHeader === undefined) { + .find(header => (header[0] || '').trim() === contentLengthHeaderName); + const contentLength = (contentLengthHeader || [])[1]; + if (contentLength === undefined) { await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`); continue main_loop; } headersLength = headers.length + headerSeparator.length * 2; - messageLength = parseInt(contentLengthHeader[1], 10); + messageLength = parseInt(contentLength, 10); } while (buffer.length < (headersLength + messageLength)) { @@ -120,6 +123,7 @@ async function* readStdin() { const messageEnd = headersLength + messageLength; const message = buffer.subarray(headersLength, messageEnd); buffer = buffer.subarray(messageEnd); + headersLength = null; messageLength = null; yield message.toString('utf8'); } @@ -188,13 +192,12 @@ function makeError(message) { } function sendResponse(response) { - let responsePayloadString = JSON.stringify({ + const responsePayloadString = JSON.stringify({ jsonrpc: "2.0", ...response }); - let headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; - let dataToSend = headers + responsePayloadString; - process.stdout.write(dataToSend); + const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`; + process.stdout.write(headers + responsePayloadString); } function loadPrettier(prettierPath) { From 7f4ebf50d3cee3d34733d43e48f19d8c0a0d6122 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 12:40:35 +0300 Subject: [PATCH 121/141] Make the first prettier test pass --- crates/editor/src/editor_tests.rs | 27 +++--- crates/node_runtime/src/node_runtime.rs | 107 +++++++++++++++++++++--- crates/prettier/src/prettier.rs | 5 +- crates/project/src/project.rs | 14 +++- 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ddae186679..4cb0c009bb 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7830,11 +7830,12 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { Some(tree_sitter_rust::language()), ); + let test_plugin = "test_plugin"; let _ = language .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { enabled_formatters: vec![BundledFormatter::Prettier { parser_name: Some("test_parser"), - plugin_names: vec!["test_plugin"], + plugin_names: vec![test_plugin], }], ..Default::default() })) @@ -7843,37 +7844,31 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { let fs = FakeFs::new(cx.background()); fs.insert_file("/file.rs", Default::default()).await; - // TODO kb have to specify some test node runtime let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let prettier_format_suffix = project.update(cx, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(Arc::new(language)); + suffix + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await .unwrap(); + let buffer_text = "one\ntwo\nthree\n"; let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); + editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx)); let format = editor.update(cx, |editor, cx| { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( - editor.read_with(cx, |editor, cx| editor.text(cx)), - "one, two\nthree\n" - ); - editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx)); - let format = editor.update(cx, |editor, cx| { - editor.perform_format(project, FormatTrigger::Manual, cx) - }); - cx.foreground().advance_clock(super::FORMAT_TIMEOUT); - cx.foreground().start_waiting(); - format.await.unwrap(); assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), - "one\ntwo\nthree\n" + buffer_text.to_string() + prettier_format_suffix, + "Test prettier formatting was not applied to the original buffer text", ); } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 125318dd67..dcb8833f8c 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -220,18 +220,83 @@ impl NodeRuntime for RealNodeRuntime { } } -pub struct FakeNodeRuntime; +pub struct FakeNodeRuntime(Option); + +struct PrettierSupport { + plugins: Vec<&'static str>, +} impl FakeNodeRuntime { pub fn new() -> Arc { - Arc::new(FakeNodeRuntime) + Arc::new(FakeNodeRuntime(None)) + } + + pub fn with_prettier_support(plugins: &[&'static str]) -> Arc { + Arc::new(FakeNodeRuntime(Some(PrettierSupport::new(plugins)))) } } #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { async fn binary_path(&self) -> anyhow::Result { - // TODO kb move away into a separate type + a Project's setter (for test code) + if let Some(prettier_support) = &self.0 { + prettier_support.binary_path().await + } else { + unreachable!() + } + } + + async fn run_npm_subcommand( + &self, + directory: Option<&Path>, + subcommand: &str, + args: &[&str], + ) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support + .run_npm_subcommand(directory, subcommand, args) + .await + } else { + unreachable!() + } + } + + async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { + if let Some(prettier_support) = &self.0 { + prettier_support.npm_package_latest_version(name).await + } else { + unreachable!() + } + } + + async fn npm_install_packages( + &self, + directory: &Path, + packages: &[(&str, &str)], + ) -> anyhow::Result<()> { + if let Some(prettier_support) = &self.0 { + prettier_support + .npm_install_packages(directory, packages) + .await + } else { + unreachable!() + } + } +} + +impl PrettierSupport { + const PACKAGE_VERSION: &str = "0.0.1"; + + fn new(plugins: &[&'static str]) -> Self { + Self { + plugins: plugins.to_vec(), + } + } +} + +#[async_trait::async_trait] +impl NodeRuntime for PrettierSupport { + async fn binary_path(&self) -> anyhow::Result { Ok(PathBuf::from("prettier_fake_node")) } @@ -240,10 +305,10 @@ impl NodeRuntime for FakeNodeRuntime { } async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result { - if name == "prettier" { - Ok("0.0.1".to_string()) + if name == "prettier" || self.plugins.contains(&name) { + Ok(Self::PACKAGE_VERSION.to_string()) } else { - unreachable!("Unexpected package name: {name}") + panic!("Unexpected package name: {name}") } } @@ -252,10 +317,32 @@ impl NodeRuntime for FakeNodeRuntime { _: &Path, packages: &[(&str, &str)], ) -> anyhow::Result<()> { - if packages == [("prettier", "0.0.1")] { - Ok(()) - } else { - unreachable!("Unexpected packages to install: {packages:?}") + assert_eq!( + packages.len(), + self.plugins.len() + 1, + "Unexpected packages length to install: {:?}, expected `prettier` + {:?}", + packages, + self.plugins + ); + for (name, version) in packages { + assert!( + name == &"prettier" || self.plugins.contains(name), + "Unexpected package `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + name, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); + assert_eq!( + version, + &Self::PACKAGE_VERSION, + "Unexpected package version `{}` to install in packages {:?}, expected {} for `prettier` + {:?}", + version, + packages, + Self::PACKAGE_VERSION, + self.plugins + ); } + Ok(()) } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 6c1d0665e2..656c84b46c 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -71,6 +71,9 @@ impl Prettier { ".editorconfig", ]; + #[cfg(any(test, feature = "test-support"))] + pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; + pub fn remote( project_id: u64, worktree_id: Option, @@ -433,7 +436,7 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { - let formatted_text = buffer.text() + "\nformatted by test prettier"; + let formatted_text = buffer.text() + Self::FORMAT_SUFFIX; buffer.diff(formatted_text, cx) }) .await), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ebc2aaaf60..e31ecca257 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -837,6 +837,16 @@ impl Project { project } + /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes. + /// Instead, if appends the suffix to every input, this suffix is returned by this method. + #[cfg(any(test, feature = "test-support"))] + pub fn enable_test_prettier(&mut self, plugins: &[&'static str]) -> &'static str { + self.node = Some(node_runtime::FakeNodeRuntime::with_prettier_support( + plugins, + )); + Prettier::FORMAT_SUFFIX + } + fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); let mut language_formatters_to_check = Vec::new(); @@ -8442,7 +8452,7 @@ impl Project { return Some(existing_prettier); } - log::info!("Found prettier at {prettier_dir:?}, starting."); + log::info!("Found prettier in {prettier_dir:?}, starting."); let task_prettier_dir = prettier_dir.clone(); let weak_project = this.downgrade(); let new_server_id = @@ -8459,7 +8469,7 @@ impl Project { .await .context("prettier start") .map_err(Arc::new)?; - log::info!("Had started prettier in {:?}", prettier.prettier_dir()); + log::info!("Started prettier in {:?}", prettier.prettier_dir()); if let Some((project, prettier_server)) = weak_project.upgrade(&mut cx).zip(prettier.server()) From 1bfde4bfa254ca151ad2f918cb2102fefb68055e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 14:45:56 +0300 Subject: [PATCH 122/141] Add more tests --- crates/collab/src/tests/integration_tests.rs | 138 ++++++++++++++++++- crates/editor/src/editor_tests.rs | 33 ++++- 2 files changed, 162 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 4008a941dd..d6d449fd47 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -15,12 +15,14 @@ use gpui::{executor::Deterministic, test::EmptyView, AppContext, ModelHandle, Te use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, Formatter, InlayHintSettings}, - tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, - LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, + tree_sitter_rust, Anchor, BundledFormatter, Diagnostic, DiagnosticEntry, FakeLspAdapter, + Language, LanguageConfig, LineEnding, OffsetRangeExt, Point, Rope, }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; +use project::{ + search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath, +}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -4407,8 +4409,6 @@ async fn test_formatting_buffer( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - use project::FormatTrigger; - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -4511,6 +4511,134 @@ async fn test_formatting_buffer( ); } +#[gpui::test(iterations = 10)] +async fn test_prettier_formatting_buffer( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let test_plugin = "test_plugin"; + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: vec![test_plugin], + }], + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry().add(Arc::clone(&language)); + + // Here we insert a fake tree with a directory that exists on disk. This is needed + // because later we'll invoke a command, which requires passing a working directory + // that points to a valid location on disk. + let directory = env::current_dir().unwrap(); + let buffer_text = "let one = \"two\""; + client_a + .fs() + .insert_tree(&directory, json!({ "a.rs": buffer_text })) + .await; + let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; + let prettier_format_suffix = project_a.update(cx_a, |project, _| { + let suffix = project.enable_test_prettier(&[test_plugin]); + project.languages().add(language); + suffix + }); + let buffer_a = cx_a + .background() + .spawn(project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let buffer_b = cx_b + .background() + .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))) + .await + .unwrap(); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::Auto); + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::LanguageServer); + }); + }); + }); + let fake_language_server = fake_language_servers.next().await.unwrap(); + fake_language_server.handle_request::(|_, _| async move { + panic!( + "Unexpected: prettier should be preferred since it's enabled and language supports it" + ) + }); + + project_b + .update(cx_b, |project, cx| { + project.format( + HashSet::from_iter([buffer_b.clone()]), + true, + FormatTrigger::Save, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after client's request" + ); + + project_a + .update(cx_a, |project, cx| { + project.format( + HashSet::from_iter([buffer_a.clone()]), + true, + FormatTrigger::Manual, + cx, + ) + }) + .await + .unwrap(); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix, + "Prettier formatting was not applied to client buffer after host's request" + ); +} + #[gpui::test(iterations = 10)] async fn test_definition( deterministic: Arc, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4cb0c009bb..c68f72d16f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5076,7 +5076,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer) + }); let mut language = Language::new( LanguageConfig { @@ -5092,6 +5094,12 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { document_formatting_provider: Some(lsp::OneOf::Left(true)), ..Default::default() }, + // Enable Prettier formatting for the same buffer, and ensure + // LSP is called instead of Prettier. + enabled_formatters: vec![BundledFormatter::Prettier { + parser_name: Some("test_parser"), + plugin_names: Vec::new(), + }], ..Default::default() })) .await; @@ -5100,7 +5108,10 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { fs.insert_file("/file.rs", Default::default()).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); + project.update(cx, |project, _| { + project.enable_test_prettier(&[]); + project.languages().add(Arc::new(language)); + }); let buffer = project .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .await @@ -5218,7 +5229,9 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); + init_test(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -7864,12 +7877,24 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { editor.perform_format(project.clone(), FormatTrigger::Manual, cx) }); format.await.unwrap(); - assert_eq!( editor.read_with(cx, |editor, cx| editor.text(cx)), buffer_text.to_string() + prettier_format_suffix, "Test prettier formatting was not applied to the original buffer text", ); + + update_test_language_settings(cx, |settings| { + settings.defaults.formatter = Some(language_settings::Formatter::Auto) + }); + let format = editor.update(cx, |editor, cx| { + editor.perform_format(project.clone(), FormatTrigger::Manual, cx) + }); + format.await.unwrap(); + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix, + "Autoformatting (via test prettier) was not applied to the original buffer text", + ); } fn empty_range(row: usize, column: usize) -> Range { From 12d7d8db0a85f77c41b2b05944293e83eafe9f1f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:26:47 +0300 Subject: [PATCH 123/141] Make all formatting to happen on the client's buffers, as needed --- crates/language/src/proto.rs | 41 +---------- crates/prettier/src/prettier.rs | 104 +++------------------------ crates/project/src/project.rs | 122 +------------------------------- crates/rpc/proto/zed.proto | 39 +--------- crates/rpc/src/proto.rs | 8 --- 5 files changed, 14 insertions(+), 300 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 5da1d5713f..c4abe39d47 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,6 +1,6 @@ use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, - Diff, Language, + Language, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; @@ -587,42 +587,3 @@ pub fn serialize_version(version: &clock::Global) -> Vec proto::Diff { - proto::Diff { - version: serialize_version(&diff.base_version), - line_ending: serialize_line_ending(diff.line_ending) as i32, - edits: diff - .edits - .into_iter() - .map(|(range, edit)| proto::DiffEdit { - range: Some(proto::Range { - start: range.start as u64, - end: range.end as u64, - }), - edit: edit.to_string(), - }) - .collect(), - } -} - -pub fn deserialize_diff(diff: proto::Diff) -> Diff { - Diff { - base_version: deserialize_version(&diff.version), - line_ending: deserialize_line_ending( - rpc::proto::LineEnding::from_i32(diff.line_ending) - .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)), - ), - edits: diff - .edits - .into_iter() - .map(|edit| { - let range = edit.range.expect("incorrect edit without a range"); - ( - range.start as usize..range.end as usize, - Arc::from(edit.edit.as_str()), - ) - }) - .collect(), - } -} diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 656c84b46c..240ee1e895 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -3,40 +3,29 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Context; -use client::{proto, Client}; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncAppContext, ModelHandle}; use language::language_settings::language_settings; -use language::proto::deserialize_diff; use language::{Buffer, BundledFormatter, Diff}; -use lsp::request::Request; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use util::paths::DEFAULT_PRETTIER_DIR; pub enum Prettier { - Local(Local), - Remote(Remote), + Real(RealPrettier), #[cfg(any(test, feature = "test-support"))] Test(TestPrettier), } -pub struct Local { +pub struct RealPrettier { worktree_id: Option, default: bool, prettier_dir: PathBuf, server: Arc, } -pub struct Remote { - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, -} - #[cfg(any(test, feature = "test-support"))] pub struct TestPrettier { worktree_id: Option, @@ -74,20 +63,6 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; - pub fn remote( - project_id: u64, - worktree_id: Option, - prettier_dir: PathBuf, - client: Arc, - ) -> Self { - Self::Remote(Remote { - project_id, - worktree_id, - prettier_dir, - client, - }) - } - pub async fn locate( starting_path: Option, fs: Arc, @@ -249,7 +224,7 @@ impl Prettier { .spawn(server.initialize(None)) .await .context("prettier server initialization")?; - Ok(Self::Local(Local { + Ok(Self::Real(RealPrettier { worktree_id, server, default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), @@ -257,31 +232,6 @@ impl Prettier { })) } - pub async fn invoke( - &self, - buffer: Option<&ModelHandle>, - buffer_path: Option, - method: &str, - cx: &AsyncAppContext, - ) -> anyhow::Result> { - match method { - Format::METHOD => self - .format( - buffer.expect("missing buffer for format invocation"), - buffer_path, - cx, - ) - .await - .context("invoke method") - .map(Some), - ClearCache::METHOD => { - self.clear_cache().await.context("invoke method")?; - Ok(None) - } - unknown => anyhow::bail!("Unknown method {unknown}"), - } - } - pub async fn format( &self, buffer: &ModelHandle, @@ -289,7 +239,7 @@ impl Prettier { cx: &AsyncAppContext, ) -> anyhow::Result { match self { - Self::Local(local) => { + Self::Real(local) => { let params = buffer.read_with(cx, |buffer, cx| { let buffer_language = buffer.language(); let parsers_with_plugins = buffer_language @@ -418,21 +368,6 @@ impl Prettier { let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx)); Ok(diff_task.await) } - Self::Remote(remote) => buffer - .read_with(cx, |buffer, _| { - remote.client.request(proto::InvokePrettierForBuffer { - buffer_id: Some(buffer.remote_id()), - worktree_id: self.worktree_id().map(|id| id as u64), - method: Format::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - }) - .await - .context("prettier diff invoke")? - .diff - .map(deserialize_diff) - .context("missing diff after prettier diff invocation"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .read_with(cx, |buffer, cx| { @@ -445,28 +380,11 @@ impl Prettier { pub async fn clear_cache(&self) -> anyhow::Result<()> { match self { - Self::Local(local) => local + Self::Real(local) => local .server .request::(()) .await .context("prettier clear cache"), - Self::Remote(remote) => remote - .client - .request(proto::InvokePrettierForBuffer { - buffer_id: None, - worktree_id: self.worktree_id().map(|id| id as u64), - method: ClearCache::METHOD.to_string(), - project_id: remote.project_id, - prettier_path: remote.prettier_dir.to_string_lossy().to_string(), - }) - .await - .map(|response| { - debug_assert!( - response.diff.is_none(), - "Cleare cache invocation returned diff data" - ) - }) - .context("prettier invoke clear cache"), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(()), } @@ -474,8 +392,7 @@ impl Prettier { pub fn server(&self) -> Option<&Arc> { match self { - Self::Local(local) => Some(&local.server), - Self::Remote(_) => None, + Self::Real(local) => Some(&local.server), #[cfg(any(test, feature = "test-support"))] Self::Test(_) => None, } @@ -483,8 +400,7 @@ impl Prettier { pub fn is_default(&self) -> bool { match self { - Self::Local(local) => local.default, - Self::Remote(_) => false, + Self::Real(local) => local.default, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.default, } @@ -492,8 +408,7 @@ impl Prettier { pub fn prettier_dir(&self) -> &Path { match self { - Self::Local(local) => &local.prettier_dir, - Self::Remote(remote) => &remote.prettier_dir, + Self::Real(local) => &local.prettier_dir, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => &test_prettier.prettier_dir, } @@ -501,8 +416,7 @@ impl Prettier { pub fn worktree_id(&self) -> Option { match self { - Self::Local(local) => local.worktree_id, - Self::Remote(remote) => remote.worktree_id, + Self::Real(local) => local.worktree_id, #[cfg(any(test, feature = "test-support"))] Self::Test(test_prettier) => test_prettier.worktree_id, } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e31ecca257..f9e1b1ce96 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, - serialize_anchor, serialize_diff, serialize_version, split_operations, + serialize_anchor, serialize_version, split_operations, }, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, @@ -613,8 +613,6 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); - client.add_model_request_handler(Self::handle_prettier_instance_for_buffer); - client.add_model_request_handler(Self::handle_invoke_prettier); } pub fn local( @@ -8310,84 +8308,6 @@ impl Project { } } - async fn handle_prettier_instance_for_buffer( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| { - let buffer = this - .opened_buffers - .get(&envelope.payload.buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?; - anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx)) - })?; - - let prettier_path = match prettier_instance_for_buffer_task.await { - Some(prettier) => match prettier.await { - Ok(prettier) => Some(prettier.prettier_dir().display().to_string()), - Err(e) => { - anyhow::bail!("Failed to create prettier instance for remote request: {e:#}") - } - }, - None => None, - }; - Ok(proto::PrettierInstanceForBufferResponse { prettier_path }) - } - - async fn handle_invoke_prettier( - this: ModelHandle, - envelope: TypedEnvelope, - _: Arc, - mut cx: AsyncAppContext, - ) -> anyhow::Result { - let prettier = this - .read_with(&cx, |this, _| { - this.prettier_instances - .get(&( - envelope.payload.worktree_id.map(WorktreeId::from_proto), - PathBuf::from(&envelope.payload.prettier_path), - )) - .cloned() - }) - .with_context(|| { - format!( - "Missing prettier for worktree {:?} and path {:?}", - envelope.payload.worktree_id, envelope.payload.prettier_path, - ) - })? - .await; - let prettier = match prettier { - Ok(prettier) => prettier, - Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"), - }; - - let buffer = this.update(&mut cx, |this, cx| { - envelope - .payload - .buffer_id - .and_then(|id| this.opened_buffers.get(&id)) - .and_then(|buffer| buffer.upgrade(cx)) - }); - - let buffer_path = buffer.as_ref().and_then(|buffer| { - buffer.read_with(&cx, |buffer, cx| { - File::from_dyn(buffer.file()).map(|f| f.full_path(cx)) - }) - }); - - let diff = prettier - .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx) - .await - .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?; - - Ok(proto::InvokePrettierForBufferResponse { - diff: diff.map(serialize_diff), - }) - } - fn prettier_instance_for_buffer( &mut self, buffer: &ModelHandle, @@ -8531,44 +8451,8 @@ impl Project { }); Some(new_prettier_task) }) - } else if let Some(project_id) = self.remote_id() { - let client = self.client.clone(); - let request = proto::PrettierInstanceForBuffer { - project_id, - buffer_id: buffer.remote_id(), - }; - cx.spawn(|this, mut cx| async move { - match client.request(request).await { - Ok(response) => { - response - .prettier_path - .map(PathBuf::from) - .map(|prettier_path| { - let prettier_task = Task::ready( - Ok(Arc::new(Prettier::remote( - project_id, - worktree_id.map(|id| id.to_usize()), - prettier_path.clone(), - client, - ))) - .map_err(Arc::new), - ) - .shared(); - this.update(&mut cx, |project, _| { - project.prettier_instances.insert( - (worktree_id, prettier_path), - prettier_task.clone(), - ); - }); - prettier_task - }) - } - Err(e) => { - log::error!("Prettier init remote request failed: {e:#}"); - None - } - } - }) + } else if self.remote_id().is_some() { + return Task::ready(None); } else { Task::ready(Some( Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index aae207f41c..302014ab8e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,12 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; - - PrettierInstanceForBuffer prettier_instance_for_buffer = 145; - PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146; - InvokePrettierForBuffer invoke_prettier_for_buffer = 147; - InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148 + MoveChannel move_channel = 142; // Current max: 144 } } @@ -1562,35 +1557,3 @@ message UpdateDiffBase { uint64 buffer_id = 2; optional string diff_base = 3; } - -message PrettierInstanceForBuffer { - uint64 project_id = 1; - uint64 buffer_id = 2; -} - -message PrettierInstanceForBufferResponse { - optional string prettier_path = 1; -} - -message InvokePrettierForBuffer { - uint64 project_id = 1; - string prettier_path = 2; - optional uint64 buffer_id = 3; - optional uint64 worktree_id = 4; - string method = 5; -} - -message InvokePrettierForBufferResponse { - optional Diff diff = 1; -} - -message Diff { - repeated VectorClockEntry version = 1; - LineEnding line_ending = 2; - repeated DiffEdit edits = 3; -} - -message DiffEdit { - Range range = 1; - string edit = 2; -} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index b51f1d8ae9..e976a3d0b5 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -273,10 +273,6 @@ messages!( (UpdateChannelBufferCollaborators, Foreground), (AckBufferOperation, Background), (AckChannelMessage, Background), - (PrettierInstanceForBuffer, Background), - (InvokePrettierForBuffer, Background), - (PrettierInstanceForBufferResponse, Background), - (InvokePrettierForBufferResponse, Background), ); request_messages!( @@ -354,8 +350,6 @@ request_messages!( (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), (LeaveChannelBuffer, Ack), - (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse), - (InvokePrettierForBuffer, InvokePrettierForBufferResponse), ); entity_messages!( @@ -407,8 +401,6 @@ entity_messages!( UpdateWorktree, UpdateWorktreeSettings, UpdateDiffBase, - PrettierInstanceForBuffer, - InvokePrettierForBuffer, ); entity_messages!( From 09ef3ccf6745c4246af964845d72e9269bebab65 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 15:58:00 +0300 Subject: [PATCH 124/141] Fix tailwind prettier plugin discovery --- crates/prettier/src/prettier.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 240ee1e895..c3811b567b 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -283,9 +283,11 @@ impl Prettier { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); for possible_plugin_path in [ prettier_plugin_dir.join("dist").join("index.mjs"), + prettier_plugin_dir.join("dist").join("index.js"), + prettier_plugin_dir.join("dist").join("plugin.js"), prettier_plugin_dir.join("index.mjs"), - prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir.join("index.js"), + prettier_plugin_dir.join("plugin.js"), prettier_plugin_dir, ] { if possible_plugin_path.is_file() { From 7aea95704eb53b35f2d956c30b829a1dc4e91829 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:15:42 +0300 Subject: [PATCH 125/141] Revert unnecessary style changes --- crates/fs/src/fs.rs | 3 +-- crates/language/src/buffer.rs | 4 ++-- crates/rpc/proto/zed.proto | 2 +- crates/rpc/src/proto.rs | 4 ++-- crates/zed/src/languages/php.rs | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index bb5d6387e0..1bc8fa9a24 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -229,12 +229,11 @@ impl Fs for RealFs { } else { symlink_metadata }; - let file_type_metadata = metadata.file_type(); Ok(Some(Metadata { inode: metadata.ino(), mtime: metadata.modified().unwrap(), is_symlink, - is_dir: file_type_metadata.is_dir(), + is_dir: metadata.file_type().is_dir(), })) } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index eccfb0c059..207c41e7cd 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -317,8 +317,8 @@ pub struct Chunk<'a> { pub struct Diff { pub(crate) base_version: clock::Global, - pub(crate) line_ending: LineEnding, - pub(crate) edits: Vec<(Range, Arc)>, + line_ending: LineEnding, + edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 302014ab8e..3501e70e6a 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,7 +170,7 @@ message Envelope { LinkChannel link_channel = 140; UnlinkChannel unlink_channel = 141; - MoveChannel move_channel = 142; // Current max: 144 + MoveChannel move_channel = 142; // current max: 144 } } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e976a3d0b5..f0d7937f6f 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -349,7 +349,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (JoinChannelBuffer, JoinChannelBufferResponse), - (LeaveChannelBuffer, Ack), + (LeaveChannelBuffer, Ack) ); entity_messages!( @@ -400,7 +400,7 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, - UpdateDiffBase, + UpdateDiffBase ); entity_messages!( diff --git a/crates/zed/src/languages/php.rs b/crates/zed/src/languages/php.rs index bf65deb642..3096fd16e6 100644 --- a/crates/zed/src/languages/php.rs +++ b/crates/zed/src/languages/php.rs @@ -100,7 +100,6 @@ impl LspAdapter for IntelephenseLspAdapter { async fn initialization_options(&self) -> Option { None } - async fn language_ids(&self) -> HashMap { HashMap::from_iter([("PHP".into(), "php".into())]) } From ef73bf799c68425ea9f1a8ec2d6f9cecb367fba0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Oct 2023 16:26:28 +0300 Subject: [PATCH 126/141] Fix license issue --- crates/prettier/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 764bf0f07f..997fa87126 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -2,9 +2,11 @@ name = "prettier" version = "0.1.0" edition = "2021" +publish = false [lib] path = "src/prettier.rs" +doctest = false [features] test-support = [] From 4688a94a54501d4604b8aad6de77aecc8d556c7d Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 12:11:27 -0400 Subject: [PATCH 127/141] Allow file links in markdown & filter links a bit aggressively --- crates/editor/src/editor.rs | 38 ++++++++++++++++----- crates/editor/src/element.rs | 10 ++++-- crates/editor/src/hover_popover.rs | 8 +++-- crates/language/src/markdown.rs | 41 ++++++++++++++++++----- crates/terminal_view/src/terminal_view.rs | 7 ++++ 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c1e0b3c18..0748a0fcf4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -122,6 +122,7 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( parsed: &language::ParsedMarkdown, editor_style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> Text { enum RenderedMarkdown {} @@ -147,15 +148,22 @@ pub fn render_parsed_markdown( region_id += 1; let region = parsed.regions[ix].clone(); - if let Some(url) = region.link_url { + if let Some(link) = region.link { cx.scene().push_cursor_region(CursorRegion { bounds, style: CursorStyle::PointingHand, }); cx.scene().push_mouse_region( MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds) - .on_down::(MouseButton::Left, move |_, _, cx| { - cx.platform().open_url(&url) + .on_down::(MouseButton::Left, move |_, _, cx| match &link { + markdown::Link::Web { url } => cx.platform().open_url(url), + markdown::Link::Path { path } => { + if let Some(workspace) = &workspace { + _ = workspace.update(cx, |workspace, cx| { + workspace.open_abs_path(path.clone(), false, cx).detach(); + }); + } + } }), ); } @@ -916,10 +924,11 @@ impl ContextMenu { &self, cursor_position: DisplayPoint, style: EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { match self { - ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } @@ -1105,7 +1114,12 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, cx: &mut ViewContext) -> AnyElement { + fn render( + &self, + style: EditorStyle, + workspace: Option>, + cx: &mut ViewContext, + ) -> AnyElement { enum CompletionTag {} let widest_completion_ix = self @@ -1278,7 +1292,7 @@ impl CompletionsMenu { Flex::column() .scrollable::(0, None, cx) .with_child(render_parsed_markdown::( - parsed, &style, cx, + parsed, &style, workspace, cx, )) .contained() .with_style(style.autocomplete.alongside_docs_container) @@ -3140,6 +3154,7 @@ impl Editor { false }); } + fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option { let offset = position.to_offset(buffer); let (word_range, kind) = buffer.surrounding_word(offset); @@ -4215,9 +4230,14 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu - .as_ref() - .map(|menu| menu.render(cursor_position, style, cx)) + self.context_menu.as_ref().map(|menu| { + menu.render( + cursor_position, + style, + self.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) + }) } fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 924d66c21c..316e143413 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2439,9 +2439,13 @@ impl Element for EditorElement { } let visible_rows = start_row..start_row + line_layouts.len() as u32; - let mut hover = editor - .hover_state - .render(&snapshot, &style, visible_rows, cx); + let mut hover = editor.hover_state.render( + &snapshot, + &style, + visible_rows, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ); let mode = editor.mode; let mut fold_indicators = editor.render_fold_indicators( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index e8901ad6c1..00a307df68 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -9,7 +9,7 @@ use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, platform::{CursorStyle, MouseButton}, - AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, + AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle, }; use language::{ markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, @@ -17,6 +17,7 @@ use language::{ use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; +use workspace::Workspace; pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; @@ -422,6 +423,7 @@ impl HoverState { snapshot: &EditorSnapshot, style: &EditorStyle, visible_rows: Range, + workspace: Option>, cx: &mut ViewContext, ) -> Option<(DisplayPoint, Vec>)> { // If there is a diagnostic, position the popovers based on that. @@ -451,7 +453,7 @@ impl HoverState { elements.push(diagnostic_popover.render(style, cx)); } if let Some(info_popover) = self.info_popover.as_mut() { - elements.push(info_popover.render(style, cx)); + elements.push(info_popover.render(style, workspace, cx)); } Some((point, elements)) @@ -470,6 +472,7 @@ impl InfoPopover { pub fn render( &mut self, style: &EditorStyle, + workspace: Option>, cx: &mut ViewContext, ) -> AnyElement { MouseEventHandler::new::(0, cx, |_, cx| { @@ -478,6 +481,7 @@ impl InfoPopover { .with_child(crate::render_parsed_markdown::( &self.parsed_content, style, + workspace, cx, )) .contained() diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index 8be15e81f6..7f57eba309 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,5 +1,5 @@ -use std::ops::Range; use std::sync::Arc; +use std::{ops::Range, path::PathBuf}; use crate::{HighlightId, Language, LanguageRegistry}; use gpui::fonts::{self, HighlightStyle, Weight}; @@ -58,7 +58,28 @@ pub struct MarkdownHighlightStyle { #[derive(Debug, Clone)] pub struct ParsedRegion { pub code: bool, - pub link_url: Option, + pub link: Option, +} + +#[derive(Debug, Clone)] +pub enum Link { + Web { url: String }, + Path { path: PathBuf }, +} + +impl Link { + fn identify(text: String) -> Option { + if text.starts_with("http") { + return Some(Link::Web { url: text }); + } + + let path = PathBuf::from(text); + if path.is_absolute() { + return Some(Link::Path { path }); + } + + None + } } pub async fn parse_markdown( @@ -115,17 +136,20 @@ pub async fn parse_markdown_block( text.push_str(t.as_ref()); let mut style = MarkdownHighlightStyle::default(); + if bold_depth > 0 { style.weight = Weight::BOLD; } + if italic_depth > 0 { style.italic = true; } - if let Some(link_url) = link_url.clone() { + + if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) { region_ranges.push(prev_len..text.len()); regions.push(ParsedRegion { - link_url: Some(link_url), code: false, + link: Some(link), }); style.underline = true; } @@ -151,7 +175,9 @@ pub async fn parse_markdown_block( Event::Code(t) => { text.push_str(t.as_ref()); region_ranges.push(prev_len..text.len()); - if link_url.is_some() { + + let link = link_url.clone().and_then(|u| Link::identify(u)); + if link.is_some() { highlights.push(( prev_len..text.len(), MarkdownHighlight::Style(MarkdownHighlightStyle { @@ -160,10 +186,7 @@ pub async fn parse_markdown_block( }), )); } - regions.push(ParsedRegion { - code: true, - link_url: link_url.clone(), - }); + regions.push(ParsedRegion { code: true, link }); } Event::Start(tag) => match tag { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index cd939b5604..5a13efd07a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -150,11 +150,14 @@ impl TerminalView { cx.notify(); cx.emit(Event::Wakeup); } + Event::Bell => { this.has_bell = true; cx.emit(Event::Wakeup); } + Event::BlinkChanged => this.blinking_on = !this.blinking_on, + Event::TitleChanged => { if let Some(foreground_info) = &this.terminal().read(cx).foreground_process_info { let cwd = foreground_info.cwd.clone(); @@ -171,6 +174,7 @@ impl TerminalView { .detach(); } } + Event::NewNavigationTarget(maybe_navigation_target) => { this.can_navigate_to_selected_word = match maybe_navigation_target { Some(MaybeNavigationTarget::Url(_)) => true, @@ -180,8 +184,10 @@ impl TerminalView { None => false, } } + Event::Open(maybe_navigation_target) => match maybe_navigation_target { MaybeNavigationTarget::Url(url) => cx.platform().open_url(url), + MaybeNavigationTarget::PathLike(maybe_path) => { if !this.can_navigate_to_selected_word { return; @@ -246,6 +252,7 @@ impl TerminalView { } } }, + _ => cx.emit(event.clone()), }) .detach(); From 85332eacbd861847582526cec0642f2d76e88944 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 13:23:26 -0400 Subject: [PATCH 128/141] Race completion filter w/completion request & make not block UI --- crates/editor/src/editor.rs | 107 +++++++++++++++++++----------- crates/editor/src/editor_tests.rs | 14 ++-- crates/editor/src/element.rs | 2 +- 3 files changed, 76 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0748a0fcf4..d7ef82da36 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -656,7 +656,7 @@ pub struct Editor { background_highlights: BTreeMap, inlay_background_highlights: TreeMap, InlayBackgroundHighlight>, nav_history: Option, - context_menu: Option, + context_menu: RwLock>, mouse_context_menu: ViewHandle, completion_tasks: Vec<(CompletionId, Task>)>, next_completion_id: CompletionId, @@ -934,12 +934,13 @@ impl ContextMenu { } } +#[derive(Clone)] struct CompletionsMenu { id: CompletionId, initial_position: Anchor, buffer: ModelHandle, completions: Arc>>, - match_candidates: Vec, + match_candidates: Arc<[StringMatchCandidate]>, matches: Arc<[StringMatch]>, selected_item: usize, list: UniformListState, @@ -1333,13 +1334,13 @@ impl CompletionsMenu { .collect() }; - //Remove all candidates where the query's start does not match the start of any word in the candidate + // Remove all candidates where the query's start does not match the start of any word in the candidate if let Some(query) = query { if let Some(query_start) = query.chars().next() { matches.retain(|string_match| { split_words(&string_match.string).any(|word| { - //Check that the first codepoint of the word as lowercase matches the first - //codepoint of the query as lowercase + // Check that the first codepoint of the word as lowercase matches the first + // codepoint of the query as lowercase word.chars() .flat_map(|codepoint| codepoint.to_lowercase()) .zip(query_start.to_lowercase()) @@ -1805,7 +1806,7 @@ impl Editor { background_highlights: Default::default(), inlay_background_highlights: Default::default(), nav_history: None, - context_menu: None, + context_menu: RwLock::new(None), mouse_context_menu: cx .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), completion_tasks: Default::default(), @@ -2100,10 +2101,12 @@ impl Editor { if local { let new_cursor_position = self.selections.newest_anchor().head(); - let completion_menu = match self.context_menu.as_mut() { + let mut context_menu = self.context_menu.write(); + let completion_menu = match context_menu.as_ref() { Some(ContextMenu::Completions(menu)) => Some(menu), + _ => { - self.context_menu.take(); + *context_menu = None; None } }; @@ -2115,13 +2118,39 @@ impl Editor { if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) { + let mut completion_menu = completion_menu.clone(); + drop(context_menu); + let query = Self::completion_query(buffer, cursor_position); - cx.background() - .block(completion_menu.filter(query.as_deref(), cx.background().clone())); + cx.spawn(move |this, mut cx| async move { + completion_menu + .filter(query.as_deref(), cx.background().clone()) + .await; + + this.update(&mut cx, |this, cx| { + let mut context_menu = this.context_menu.write(); + let Some(ContextMenu::Completions(menu)) = context_menu.as_ref() else { + return; + }; + + if menu.id > completion_menu.id { + return; + } + + *context_menu = Some(ContextMenu::Completions(completion_menu)); + drop(context_menu); + cx.notify(); + }) + }) + .detach(); + self.show_completions(&ShowCompletions, cx); } else { + drop(context_menu); self.hide_context_menu(cx); } + } else { + drop(context_menu); } hide_hover(self, cx); @@ -3432,23 +3461,31 @@ impl Editor { this.update(&mut cx, |this, cx| { this.completion_tasks.retain(|(task_id, _)| *task_id > id); - match this.context_menu.as_ref() { + let mut context_menu = this.context_menu.write(); + match context_menu.as_ref() { None => {} + Some(ContextMenu::Completions(prev_menu)) => { if prev_menu.id > id { return; } } + _ => return, } if this.focused && menu.is_some() { let menu = menu.unwrap(); - this.show_context_menu(ContextMenu::Completions(menu), cx); + *context_menu = Some(ContextMenu::Completions(menu)); + drop(context_menu); + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + cx.notify(); } else if this.completion_tasks.is_empty() { // If there are no more completion tasks and the last menu was // empty, we should hide it. If it was already hidden, we should // also show the copilot suggestion when available. + drop(context_menu); if this.hide_context_menu(cx).is_none() { this.update_visible_copilot_suggestion(cx); } @@ -3593,14 +3630,13 @@ impl Editor { } pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { - if matches!( - self.context_menu.as_ref(), - Some(ContextMenu::CodeActions(_)) - ) { - self.context_menu.take(); + let mut context_menu = self.context_menu.write(); + if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + *context_menu = None; cx.notify(); return; } + drop(context_menu); let deployed_from_indicator = action.deployed_from_indicator; let mut task = self.code_actions_task.take(); @@ -3613,16 +3649,16 @@ impl Editor { this.update(&mut cx, |this, cx| { if this.focused { if let Some((buffer, actions)) = this.available_code_actions.clone() { - this.show_context_menu( - ContextMenu::CodeActions(CodeActionsMenu { + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + *this.context_menu.write() = + Some(ContextMenu::CodeActions(CodeActionsMenu { buffer, actions, selected_item: Default::default(), list: Default::default(), deployed_from_indicator, - }), - cx, - ); + })); } } })?; @@ -4086,7 +4122,7 @@ impl Editor { let selection = self.selections.newest_anchor(); let cursor = selection.head(); - if self.context_menu.is_some() + if self.context_menu.read().is_some() || !self.completion_tasks.is_empty() || selection.start != selection.end { @@ -4220,6 +4256,7 @@ impl Editor { pub fn context_menu_visible(&self) -> bool { self.context_menu + .read() .as_ref() .map_or(false, |menu| menu.visible()) } @@ -4230,7 +4267,7 @@ impl Editor { style: EditorStyle, cx: &mut ViewContext, ) -> Option<(DisplayPoint, AnyElement)> { - self.context_menu.as_ref().map(|menu| { + self.context_menu.read().as_ref().map(|menu| { menu.render( cursor_position, style, @@ -4240,19 +4277,10 @@ impl Editor { }) } - fn show_context_menu(&mut self, menu: ContextMenu, cx: &mut ViewContext) { - if !matches!(menu, ContextMenu::Completions(_)) { - self.completion_tasks.clear(); - } - self.context_menu = Some(menu); - self.discard_copilot_suggestion(cx); - cx.notify(); - } - fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { cx.notify(); self.completion_tasks.clear(); - let context_menu = self.context_menu.take(); + let context_menu = self.context_menu.write().take(); if context_menu.is_some() { self.update_visible_copilot_suggestion(cx); } @@ -5604,6 +5632,7 @@ impl Editor { if self .context_menu + .write() .as_mut() .map(|menu| menu.select_last(self.project.as_ref(), cx)) .unwrap_or(false) @@ -5648,25 +5677,25 @@ impl Editor { } pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_first(self.project.as_ref(), cx); } } pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_prev(self.project.as_ref(), cx); } } pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_next(self.project.as_ref(), cx); } } pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - if let Some(context_menu) = self.context_menu.as_mut() { + if let Some(context_menu) = self.context_menu.write().as_mut() { context_menu.select_last(self.project.as_ref(), cx); } } @@ -9164,7 +9193,7 @@ impl View for Editor { keymap.add_identifier("renaming"); } if self.context_menu_visible() { - match self.context_menu.as_ref() { + match self.context_menu.read().as_ref() { Some(ContextMenu::Completions(_)) => { keymap.add_identifier("menu"); keymap.add_identifier("showing_completions") diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dee27e0121..4be29ea084 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5430,9 +5430,9 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { additional edit "}); cx.simulate_keystroke(" "); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("s"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.assert_editor_state(indoc! {" one.second_completion @@ -5494,12 +5494,12 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { }); cx.set_state("editorˇ"); cx.simulate_keystroke("."); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.simulate_keystroke("c"); cx.simulate_keystroke("l"); cx.simulate_keystroke("o"); cx.assert_editor_state("editor.cloˇ"); - assert!(cx.editor(|e, _| e.context_menu.is_none())); + assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.update_editor(|editor, cx| { editor.show_completions(&ShowCompletions, cx); }); @@ -7788,7 +7788,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("-"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-red", "bg-blue", "bg-yellow"] @@ -7801,7 +7801,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-blue", "bg-yellow"] @@ -7817,7 +7817,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui: cx.simulate_keystroke("l"); cx.foreground().run_until_parked(); cx.update_editor(|editor, _| { - if let Some(ContextMenu::Completions(menu)) = &editor.context_menu { + if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() { assert_eq!( menu.matches.iter().map(|m| &m.string).collect::>(), &["bg-yellow"] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 316e143413..00c8508b6c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2428,7 +2428,7 @@ impl Element for EditorElement { } let active = matches!( - editor.context_menu, + editor.context_menu.read().as_ref(), Some(crate::ContextMenu::CodeActions(_)) ); From 2e5461ee4de8f296c116e00a0c70efaacd376227 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 11:55:39 -0700 Subject: [PATCH 129/141] Exclude disconnected channel views from following messages --- crates/collab_ui/src/channel_view.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index b2e65eb2fa..817e9fcb4e 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -285,10 +285,14 @@ impl FollowableItem for ChannelView { } fn to_state_proto(&self, cx: &AppContext) -> Option { - let channel = self.channel_buffer.read(cx).channel(); + let channel_buffer = self.channel_buffer.read(cx); + if !channel_buffer.is_connected() { + return None; + } + Some(proto::view::Variant::ChannelView( proto::view::ChannelView { - channel_id: channel.id, + channel_id: channel_buffer.channel().id, editor: if let Some(proto::view::Variant::Editor(proto)) = self.editor.read(cx).to_state_proto(cx) { From 85fe11ff117f076ae5dc92a7a99607d14e0d6e7e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:38:23 -0700 Subject: [PATCH 130/141] Replace disconnected channel notes views when re-opening the notes --- crates/channel/src/channel_buffer.rs | 4 +++ crates/collab_ui/src/channel_view.rs | 41 +++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index 7de8b956f1..ab7ea78ac1 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -99,6 +99,10 @@ impl ChannelBuffer { })) } + pub fn remote_id(&self, cx: &AppContext) -> u64 { + self.buffer.read(cx).remote_id() + } + pub fn user_store(&self) -> &ModelHandle { &self.user_store } diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 817e9fcb4e..e62ee8ef4b 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -24,7 +24,7 @@ use workspace::{ item::{FollowableItem, Item, ItemHandle}, register_followable_item, searchable::SearchableItemHandle, - ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId, + ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId, }; actions!(channel_view, [Deploy]); @@ -93,15 +93,36 @@ impl ChannelView { } pane.update(&mut cx, |pane, cx| { - pane.items_of_type::() - .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer) - .unwrap_or_else(|| { - cx.add_view(|cx| { - let mut this = Self::new(project, channel_store, channel_buffer, cx); - this.acknowledge_buffer_version(cx); - this - }) - }) + let buffer_id = channel_buffer.read(cx).remote_id(cx); + + let existing_view = pane + .items_of_type::() + .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id); + + // If this channel buffer is already open in this pane, just return it. + if let Some(existing_view) = existing_view.clone() { + if existing_view.read(cx).channel_buffer == channel_buffer { + return existing_view; + } + } + + let view = cx.add_view(|cx| { + let mut this = Self::new(project, channel_store, channel_buffer, cx); + this.acknowledge_buffer_version(cx); + this + }); + + // If the pane contained a disconnected view for this channel buffer, + // replace that. + if let Some(existing_item) = existing_view { + if let Some(ix) = pane.index_for_item(&existing_item) { + pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx) + .detach(); + pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx); + } + } + + view }) .ok_or_else(|| anyhow!("pane was dropped")) }) From f5d6d7caca2ce1d9f2bf4db75bd8cc461c5567f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Oct 2023 12:39:02 -0700 Subject: [PATCH 131/141] Mark channel notes as disconnected immediately upon explicitly signing out --- crates/channel/src/channel_store.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 2a2fa454f2..bceb2c094d 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -114,12 +114,21 @@ impl ChannelStore { let watch_connection_status = cx.spawn_weak(|this, mut cx| async move { while let Some(status) = connection_status.next().await { let this = this.upgrade(&cx)?; + match status { + client::Status::Connected { .. } => { + this.update(&mut cx, |this, cx| this.handle_connect(cx)) + .await + .log_err()?; + } + client::Status::SignedOut | client::Status::UpgradeRequired => { + this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx)); + } + _ => { + this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx)); + } + } if status.is_connected() { - this.update(&mut cx, |this, cx| this.handle_connect(cx)) - .await - .log_err()?; } else { - this.update(&mut cx, |this, cx| this.handle_disconnect(cx)); } } Some(()) @@ -823,7 +832,7 @@ impl ChannelStore { }) } - fn handle_disconnect(&mut self, cx: &mut ModelContext) { + fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext) { self.channel_index.clear(); self.channel_invitations.clear(); self.channel_participants.clear(); @@ -834,7 +843,10 @@ impl ChannelStore { self.disconnect_channel_buffers_task.get_or_insert_with(|| { cx.spawn_weak(|this, mut cx| async move { - cx.background().timer(RECONNECT_TIMEOUT).await; + if wait_for_reconnect { + cx.background().timer(RECONNECT_TIMEOUT).await; + } + if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { for (_, buffer) in this.opened_buffers.drain() { From d23bb3b05da84c29ce9626f4d7a68461f2e19c93 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 16:18:54 -0400 Subject: [PATCH 132/141] Unbork markdown parse test by making links match --- crates/editor/src/hover_popover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 00a307df68..5b3985edf9 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -911,7 +911,7 @@ mod tests { // Links Row { blocks: vec![HoverBlock { - text: "one [two](the-url) three".to_string(), + text: "one [two](https://the-url) three".to_string(), kind: HoverBlockKind::Markdown, }], expected_marked_text: "one «two» three".to_string(), @@ -932,7 +932,7 @@ mod tests { - a - b * two - - [c](the-url) + - [c](https://the-url) - d" .unindent(), kind: HoverBlockKind::Markdown, From 45f3a9835972b42763bc95d3759703d2f4f5f379 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 12 Oct 2023 17:40:20 -0400 Subject: [PATCH 133/141] Remove old `ui` and `storybook` crates (#3125) This PR deletes the old `ui` and `storybook` crates in favor of their newer variants that we'll be landing to `main` in the near future. ### Motivation These crates are based off the old version of GPUI 2 (the `gpui2` crate). At this point we have since transitioned to the new version of GPUI 2 (the `gpui3` crate, currently still on the `gpui2` branch). Having both copies around is confusing, so the old ones are going the way of the dinosaurs. Release Notes: - N/A --- Cargo.lock | 59 - Cargo.toml | 2 - crates/storybook/Cargo.lock | 2919 ----------------- crates/storybook/Cargo.toml | 30 - crates/storybook/docs/thoughts.md | 72 - crates/storybook/src/stories.rs | 3 - crates/storybook/src/stories/components.rs | 22 - .../src/stories/components/assistant_panel.rs | 16 - .../src/stories/components/breadcrumb.rs | 45 - .../src/stories/components/buffer.rs | 36 - .../src/stories/components/chat_panel.rs | 46 - .../src/stories/components/collab_panel.rs | 16 - .../src/stories/components/context_menu.rs | 21 - .../src/stories/components/facepile.rs | 25 - .../src/stories/components/keybinding.rs | 64 - .../stories/components/language_selector.rs | 16 - .../src/stories/components/multi_buffer.rs | 24 - .../src/stories/components/palette.rs | 53 - .../storybook/src/stories/components/panel.rs | 25 - .../src/stories/components/project_panel.rs | 20 - .../src/stories/components/recent_projects.rs | 16 - .../src/stories/components/status_bar.rs | 16 - .../storybook/src/stories/components/tab.rs | 91 - .../src/stories/components/tab_bar.rs | 46 - .../src/stories/components/terminal.rs | 16 - .../src/stories/components/theme_selector.rs | 16 - .../src/stories/components/title_bar.rs | 16 - .../src/stories/components/toolbar.rs | 70 - .../src/stories/components/traffic_lights.rs | 18 - crates/storybook/src/stories/elements.rs | 5 - .../storybook/src/stories/elements/avatar.rs | 23 - .../storybook/src/stories/elements/button.rs | 192 -- crates/storybook/src/stories/elements/icon.rs | 19 - .../storybook/src/stories/elements/input.rs | 16 - .../storybook/src/stories/elements/label.rs | 18 - crates/storybook/src/stories/kitchen_sink.rs | 26 - crates/storybook/src/story.rs | 44 - crates/storybook/src/story_selector.rs | 178 - crates/storybook/src/storybook.rs | 198 -- crates/ui/Cargo.toml | 16 - crates/ui/docs/_project.md | 13 - crates/ui/docs/elevation.md | 57 - crates/ui/src/children.rs | 7 - crates/ui/src/components.rs | 163 - crates/ui/src/components/assistant_panel.rs | 91 - crates/ui/src/components/breadcrumb.rs | 71 - crates/ui/src/components/buffer.rs | 233 -- crates/ui/src/components/chat_panel.rs | 108 - crates/ui/src/components/collab_panel.rs | 161 - crates/ui/src/components/command_palette.rs | 29 - crates/ui/src/components/context_menu.rs | 65 - crates/ui/src/components/editor_pane.rs | 60 - crates/ui/src/components/facepile.rs | 28 - crates/ui/src/components/icon_button.rs | 67 - crates/ui/src/components/keybinding.rs | 158 - crates/ui/src/components/language_selector.rs | 36 - crates/ui/src/components/list.rs | 512 --- crates/ui/src/components/multi_buffer.rs | 42 - crates/ui/src/components/palette.rs | 152 - crates/ui/src/components/panel.rs | 142 - crates/ui/src/components/panes.rs | 132 - crates/ui/src/components/player_stack.rs | 65 - crates/ui/src/components/project_panel.rs | 58 - crates/ui/src/components/recent_projects.rs | 32 - crates/ui/src/components/status_bar.rs | 144 - crates/ui/src/components/tab.rs | 131 - crates/ui/src/components/tab_bar.rs | 85 - crates/ui/src/components/terminal.rs | 84 - crates/ui/src/components/theme_selector.rs | 37 - crates/ui/src/components/title_bar.rs | 117 - crates/ui/src/components/toast.rs | 66 - crates/ui/src/components/toolbar.rs | 49 - crates/ui/src/components/traffic_lights.rs | 78 - crates/ui/src/components/workspace.rs | 186 -- crates/ui/src/element_ext.rs | 24 - crates/ui/src/elements.rs | 19 - crates/ui/src/elements/avatar.rs | 41 - crates/ui/src/elements/button.rs | 203 -- crates/ui/src/elements/details.rs | 33 - crates/ui/src/elements/icon.rs | 185 -- crates/ui/src/elements/input.rs | 106 - crates/ui/src/elements/label.rs | 161 - crates/ui/src/elements/player.rs | 133 - crates/ui/src/elements/stack.rs | 31 - crates/ui/src/elements/tool_divider.rs | 17 - crates/ui/src/lib.rs | 20 - crates/ui/src/prelude.rs | 274 -- crates/ui/src/static_data.rs | 966 ------ crates/ui/src/theme.rs | 196 -- crates/ui/src/tokens.rs | 25 - crates/ui/tracker.md | 133 - 91 files changed, 10580 deletions(-) delete mode 100644 crates/storybook/Cargo.lock delete mode 100644 crates/storybook/Cargo.toml delete mode 100644 crates/storybook/docs/thoughts.md delete mode 100644 crates/storybook/src/stories.rs delete mode 100644 crates/storybook/src/stories/components.rs delete mode 100644 crates/storybook/src/stories/components/assistant_panel.rs delete mode 100644 crates/storybook/src/stories/components/breadcrumb.rs delete mode 100644 crates/storybook/src/stories/components/buffer.rs delete mode 100644 crates/storybook/src/stories/components/chat_panel.rs delete mode 100644 crates/storybook/src/stories/components/collab_panel.rs delete mode 100644 crates/storybook/src/stories/components/context_menu.rs delete mode 100644 crates/storybook/src/stories/components/facepile.rs delete mode 100644 crates/storybook/src/stories/components/keybinding.rs delete mode 100644 crates/storybook/src/stories/components/language_selector.rs delete mode 100644 crates/storybook/src/stories/components/multi_buffer.rs delete mode 100644 crates/storybook/src/stories/components/palette.rs delete mode 100644 crates/storybook/src/stories/components/panel.rs delete mode 100644 crates/storybook/src/stories/components/project_panel.rs delete mode 100644 crates/storybook/src/stories/components/recent_projects.rs delete mode 100644 crates/storybook/src/stories/components/status_bar.rs delete mode 100644 crates/storybook/src/stories/components/tab.rs delete mode 100644 crates/storybook/src/stories/components/tab_bar.rs delete mode 100644 crates/storybook/src/stories/components/terminal.rs delete mode 100644 crates/storybook/src/stories/components/theme_selector.rs delete mode 100644 crates/storybook/src/stories/components/title_bar.rs delete mode 100644 crates/storybook/src/stories/components/toolbar.rs delete mode 100644 crates/storybook/src/stories/components/traffic_lights.rs delete mode 100644 crates/storybook/src/stories/elements.rs delete mode 100644 crates/storybook/src/stories/elements/avatar.rs delete mode 100644 crates/storybook/src/stories/elements/button.rs delete mode 100644 crates/storybook/src/stories/elements/icon.rs delete mode 100644 crates/storybook/src/stories/elements/input.rs delete mode 100644 crates/storybook/src/stories/elements/label.rs delete mode 100644 crates/storybook/src/stories/kitchen_sink.rs delete mode 100644 crates/storybook/src/story.rs delete mode 100644 crates/storybook/src/story_selector.rs delete mode 100644 crates/storybook/src/storybook.rs delete mode 100644 crates/ui/Cargo.toml delete mode 100644 crates/ui/docs/_project.md delete mode 100644 crates/ui/docs/elevation.md delete mode 100644 crates/ui/src/children.rs delete mode 100644 crates/ui/src/components.rs delete mode 100644 crates/ui/src/components/assistant_panel.rs delete mode 100644 crates/ui/src/components/breadcrumb.rs delete mode 100644 crates/ui/src/components/buffer.rs delete mode 100644 crates/ui/src/components/chat_panel.rs delete mode 100644 crates/ui/src/components/collab_panel.rs delete mode 100644 crates/ui/src/components/command_palette.rs delete mode 100644 crates/ui/src/components/context_menu.rs delete mode 100644 crates/ui/src/components/editor_pane.rs delete mode 100644 crates/ui/src/components/facepile.rs delete mode 100644 crates/ui/src/components/icon_button.rs delete mode 100644 crates/ui/src/components/keybinding.rs delete mode 100644 crates/ui/src/components/language_selector.rs delete mode 100644 crates/ui/src/components/list.rs delete mode 100644 crates/ui/src/components/multi_buffer.rs delete mode 100644 crates/ui/src/components/palette.rs delete mode 100644 crates/ui/src/components/panel.rs delete mode 100644 crates/ui/src/components/panes.rs delete mode 100644 crates/ui/src/components/player_stack.rs delete mode 100644 crates/ui/src/components/project_panel.rs delete mode 100644 crates/ui/src/components/recent_projects.rs delete mode 100644 crates/ui/src/components/status_bar.rs delete mode 100644 crates/ui/src/components/tab.rs delete mode 100644 crates/ui/src/components/tab_bar.rs delete mode 100644 crates/ui/src/components/terminal.rs delete mode 100644 crates/ui/src/components/theme_selector.rs delete mode 100644 crates/ui/src/components/title_bar.rs delete mode 100644 crates/ui/src/components/toast.rs delete mode 100644 crates/ui/src/components/toolbar.rs delete mode 100644 crates/ui/src/components/traffic_lights.rs delete mode 100644 crates/ui/src/components/workspace.rs delete mode 100644 crates/ui/src/element_ext.rs delete mode 100644 crates/ui/src/elements.rs delete mode 100644 crates/ui/src/elements/avatar.rs delete mode 100644 crates/ui/src/elements/button.rs delete mode 100644 crates/ui/src/elements/details.rs delete mode 100644 crates/ui/src/elements/icon.rs delete mode 100644 crates/ui/src/elements/input.rs delete mode 100644 crates/ui/src/elements/label.rs delete mode 100644 crates/ui/src/elements/player.rs delete mode 100644 crates/ui/src/elements/stack.rs delete mode 100644 crates/ui/src/elements/tool_divider.rs delete mode 100644 crates/ui/src/lib.rs delete mode 100644 crates/ui/src/prelude.rs delete mode 100644 crates/ui/src/static_data.rs delete mode 100644 crates/ui/src/theme.rs delete mode 100644 crates/ui/src/tokens.rs delete mode 100644 crates/ui/tracker.md diff --git a/Cargo.lock b/Cargo.lock index f95db3d354..01153ca0f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6623,12 +6623,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "rustybuzz" version = "0.3.0" @@ -7682,28 +7676,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "clap 4.4.4", - "fs", - "futures 0.3.28", - "gpui2", - "itertools 0.11.0", - "log", - "rust-embed", - "serde", - "settings", - "simplelog", - "strum", - "theme", - "ui", - "util", -] - [[package]] name = "stringprep" version = "0.1.4" @@ -7726,22 +7698,6 @@ name = "strum" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.37", -] [[package]] name = "subtle" @@ -8908,21 +8864,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ui" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "gpui2", - "rand 0.8.5", - "serde", - "settings", - "smallvec", - "strum", - "theme", -] - [[package]] name = "unicase" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index 25aec39cdd..532610efd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,13 +66,11 @@ members = [ "crates/sqlez_macros", "crates/feature_flags", "crates/rich_text", - "crates/storybook", "crates/sum_tree", "crates/terminal", "crates/text", "crates/theme", "crates/theme_selector", - "crates/ui", "crates/util", "crates/semantic_index", "crates/vim", diff --git a/crates/storybook/Cargo.lock b/crates/storybook/Cargo.lock deleted file mode 100644 index b652dbbd13..0000000000 --- a/crates/storybook/Cargo.lock +++ /dev/null @@ -1,2919 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-net" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" -dependencies = [ - "async-io", - "autocfg", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" -dependencies = [ - "async-io", - "async-lock", - "autocfg", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "signal-hook", - "windows-sys", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.1", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.25", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - -[[package]] -name = "bstr" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading 0.7.4", -] - -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" -dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "collections" -version = "0.1.0" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", - "uuid 0.5.1", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" -dependencies = [ - "bitflags", - "core-foundation", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.63+curl-8.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - -[[package]] -name = "data-url" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" -dependencies = [ - "matches", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.0", -] - -[[package]] -name = "dwrote" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "erased-serde" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3" -dependencies = [ - "serde", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "etagere" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "euclid" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "float-cmp" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" - -[[package]] -name = "float-ord" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "font-kit" -version = "0.11.0" -source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" -dependencies = [ - "bitflags", - "byteorder", - "core-foundation", - "core-graphics", - "core-text", - "dirs-next", - "dwrote", - "float-ord", - "freetype", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "fontdb" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1" -dependencies = [ - "log", - "memmap2", - "ttf-parser 0.12.3", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "freetype" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" -dependencies = [ - "freetype-sys", - "libc", -] - -[[package]] -name = "freetype-sys" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" -dependencies = [ - "cmake", - "libc", - "pkg-config", -] - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "gpui" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-task", - "bindgen", - "block", - "cc", - "cocoa", - "collections", - "core-foundation", - "core-graphics", - "core-text", - "ctor", - "etagere", - "font-kit", - "foreign-types", - "futures", - "gpui_macros", - "image", - "itertools", - "lazy_static", - "log", - "media", - "metal", - "num_cpus", - "objc", - "ordered-float", - "parking", - "parking_lot 0.11.2", - "pathfinder_color", - "pathfinder_geometry", - "postage", - "rand", - "resvg", - "schemars", - "seahash", - "serde", - "serde_derive", - "serde_json", - "smallvec", - "smol", - "sqlez", - "sum_tree", - "time", - "tiny-skia", - "usvg", - "util", - "uuid 1.4.0", - "waker-fn", -] - -[[package]] -name = "gpui_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.23.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "kurbo" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" -dependencies = [ - "arrayvec 0.7.4", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" -dependencies = [ - "cfg-if", - "windows-sys", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -dependencies = [ - "serde", - "value-bag", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "media" -version = "0.1.0" -dependencies = [ - "anyhow", - "bindgen", - "block", - "bytes", - "core-foundation", - "foreign-types", - "metal", - "objc", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "foreign-types", - "log", - "objc", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets", -] - -[[package]] -name = "pathfinder_color" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69bdc0d277d559e35e1b374de56df9262a6b71e091ca04a8831a239f8c7f0c62" -dependencies = [ - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pest" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "storybook" -version = "0.1.0" -dependencies = [ - "gpui", -] - -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", -] - -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - -[[package]] -name = "postage" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" -dependencies = [ - "atomic", - "crossbeam-queue", - "futures", - "log", - "parking_lot 0.12.1", - "pin-project", - "pollster", - "static_assertions", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettyplease" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" -dependencies = [ - "proc-macro2", - "syn 2.0.25", -] - -[[package]] -name = "proc-macro2" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "rctree" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "resvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09697862c5c3f940cbaffef91969c62188b5c8ed385b0aef43a5ff01ddc8000f" -dependencies = [ - "jpeg-decoder", - "log", - "pico-args", - "png", - "rgb", - "svgfilters", - "tiny-skia", - "usvg", -] - -[[package]] -name = "rgb" -version = "0.8.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "rust-embed" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.25", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustybuzz" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" -dependencies = [ - "bitflags", - "bytemuck", - "smallvec", - "ttf-parser 0.9.0", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-general-category", - "unicode-script", -] - -[[package]] -name = "ryu" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - -[[package]] -name = "safe_arch" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "schemars" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "serde" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_fmt" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "simplecss" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - -[[package]] -name = "smallvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" - -[[package]] -name = "smol" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "sqlez" -version = "0.1.0" -dependencies = [ - "anyhow", - "futures", - "indoc", - "lazy_static", - "libsqlite3-sys", - "parking_lot 0.11.2", - "smol", - "thread_local", - "uuid 1.4.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "sum_tree" -version = "0.1.0" -dependencies = [ - "arrayvec 0.7.4", - "log", -] - -[[package]] -name = "sval" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" - -[[package]] -name = "sval_buffer" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_ref" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" -dependencies = [ - "serde", - "sval", - "sval_buffer", - "sval_fmt", -] - -[[package]] -name = "svg_fmt" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" - -[[package]] -name = "svgfilters" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0dce2fee79ac40c21dafba48565ff7a5fa275e23ffe9ce047a40c9574ba34e" -dependencies = [ - "float-cmp", - "rgb", -] - -[[package]] -name = "svgtypes" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" -dependencies = [ - "float-cmp", - "siphasher", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "take-until" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" - -[[package]] -name = "thiserror" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.4", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" - -[[package]] -name = "time-macros" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" -dependencies = [ - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf81f2900d2e235220e6f31ec9f63ade6a7f59090c556d74fe949bb3b15e9fe" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "bytemuck", - "cfg-if", - "png", - "safe_arch", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "ttf-parser" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca" - -[[package]] -name = "ttf-parser" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - -[[package]] -name = "unicode-general-category" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" - -[[package]] -name = "unicode-ident" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-script" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "usvg" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "kurbo", - "log", - "memmap2", - "pico-args", - "rctree", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "svgtypes", - "ttf-parser 0.12.3", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "util" -version = "0.1.0" -dependencies = [ - "anyhow", - "backtrace", - "dirs", - "futures", - "isahc", - "lazy_static", - "log", - "rand", - "rust-embed", - "serde", - "serde_json", - "smol", - "take-until", - "url", -] - -[[package]] -name = "uuid" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" - -[[package]] -name = "uuid" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" -dependencies = [ - "getrandom", -] - -[[package]] -name = "value-bag" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394" -dependencies = [ - "erased-serde", - "serde", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - -[[package]] -name = "xmlparser" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml deleted file mode 100644 index 43890dd01a..0000000000 --- a/crates/storybook/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "storybook" -version = "0.1.0" -edition = "2021" -publish = false - -[[bin]] -name = "storybook" -path = "src/storybook.rs" - -[dependencies] -anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } -chrono = "0.4" -fs = { path = "../fs" } -futures.workspace = true -gpui2 = { path = "../gpui2" } -itertools = "0.11.0" -log.workspace = true -rust-embed.workspace = true -serde.workspace = true -settings = { path = "../settings" } -simplelog = "0.9" -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -ui = { path = "../ui" } -util = { path = "../util" } - -[dev-dependencies] -gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/storybook/docs/thoughts.md b/crates/storybook/docs/thoughts.md deleted file mode 100644 index 9416f25933..0000000000 --- a/crates/storybook/docs/thoughts.md +++ /dev/null @@ -1,72 +0,0 @@ -Much of element styling is now handled by an external engine. - - -How do I make an element hover. - -There's a hover style. - -Hoverable needs to wrap another element. That element can be styled. - -```rs -struct Hoverable { - -} - -impl Element for Hoverable { - -} - -``` - - - -```rs -#[derive(Styled, Interactive)] -pub struct Div { - declared_style: StyleRefinement, - interactions: Interactions -} - -pub trait Styled { - fn declared_style(&mut self) -> &mut StyleRefinement; - fn compute_style(&mut self) -> Style { - Style::default().refine(self.declared_style()) - } - - // All the tailwind classes, modifying self.declared_style() -} - -impl Style { - pub fn paint_background(layout: Layout, cx: &mut PaintContext); - pub fn paint_foreground(layout: Layout, cx: &mut PaintContext); -} - -pub trait Interactive { - fn interactions(&mut self) -> &mut Interactions; - - fn on_click(self, ) -} - -struct Interactions { - click: SmallVec<[; 1]>, -} - - -``` - - -```rs - - -trait Stylable { - type Style; - - fn with_style(self, style: Self::Style) -> Self; -} - - - - - - -``` diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs deleted file mode 100644 index 95b8844157..0000000000 --- a/crates/storybook/src/stories.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod components; -pub mod elements; -pub mod kitchen_sink; diff --git a/crates/storybook/src/stories/components.rs b/crates/storybook/src/stories/components.rs deleted file mode 100644 index 85d5ce088f..0000000000 --- a/crates/storybook/src/stories/components.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod assistant_panel; -pub mod breadcrumb; -pub mod buffer; -pub mod chat_panel; -pub mod collab_panel; -pub mod context_menu; -pub mod facepile; -pub mod keybinding; -pub mod language_selector; -pub mod multi_buffer; -pub mod palette; -pub mod panel; -pub mod project_panel; -pub mod recent_projects; -pub mod status_bar; -pub mod tab; -pub mod tab_bar; -pub mod terminal; -pub mod theme_selector; -pub mod title_bar; -pub mod toolbar; -pub mod traffic_lights; diff --git a/crates/storybook/src/stories/components/assistant_panel.rs b/crates/storybook/src/stories/components/assistant_panel.rs deleted file mode 100644 index 09f964756e..0000000000 --- a/crates/storybook/src/stories/components/assistant_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::AssistantPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AssistantPanelStory {} - -impl AssistantPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, AssistantPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(AssistantPanel::new()) - } -} diff --git a/crates/storybook/src/stories/components/breadcrumb.rs b/crates/storybook/src/stories/components/breadcrumb.rs deleted file mode 100644 index 002b6140e1..0000000000 --- a/crates/storybook/src/stories/components/breadcrumb.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use ui::prelude::*; -use ui::{Breadcrumb, HighlightedText, Symbol}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BreadcrumbStory {} - -impl BreadcrumbStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Breadcrumb>(cx)) - .child(Story::label(cx, "Default")) - .child(Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "BreadcrumbStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - )) - } -} diff --git a/crates/storybook/src/stories/components/buffer.rs b/crates/storybook/src/stories/components/buffer.rs deleted file mode 100644 index 0b3268421b..0000000000 --- a/crates/storybook/src/stories/components/buffer.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gpui2::geometry::rems; -use ui::prelude::*; -use ui::{ - empty_buffer_example, hello_world_rust_buffer_example, - hello_world_rust_buffer_with_status_example, Buffer, -}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct BufferStory {} - -impl BufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, Buffer>(cx)) - .child(Story::label(cx, "Default")) - .child(div().w(rems(64.)).h_96().child(empty_buffer_example())) - .child(Story::label(cx, "Hello World (Rust)")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_example(&theme)), - ) - .child(Story::label(cx, "Hello World (Rust) with Status")) - .child( - div() - .w(rems(64.)) - .h_96() - .child(hello_world_rust_buffer_with_status_example(&theme)), - ) - } -} diff --git a/crates/storybook/src/stories/components/chat_panel.rs b/crates/storybook/src/stories/components/chat_panel.rs deleted file mode 100644 index e87ac0afa2..0000000000 --- a/crates/storybook/src/stories/components/chat_panel.rs +++ /dev/null @@ -1,46 +0,0 @@ -use chrono::DateTime; -use ui::prelude::*; -use ui::{ChatMessage, ChatPanel, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ChatPanelStory {} - -impl ChatPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ChatPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ChatPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - .child(Story::label(cx, "With Mesages")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z") - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/collab_panel.rs b/crates/storybook/src/stories/components/collab_panel.rs deleted file mode 100644 index 6a66f0d47f..0000000000 --- a/crates/storybook/src/stories/components/collab_panel.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::CollabPanel; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct CollabPanelStory {} - -impl CollabPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, CollabPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(CollabPanel::new(ScrollState::default())) - } -} diff --git a/crates/storybook/src/stories/components/context_menu.rs b/crates/storybook/src/stories/components/context_menu.rs deleted file mode 100644 index 71776ca66a..0000000000 --- a/crates/storybook/src/stories/components/context_menu.rs +++ /dev/null @@ -1,21 +0,0 @@ -use ui::prelude::*; -use ui::{ContextMenu, ContextMenuItem, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ContextMenuStory {} - -impl ContextMenuStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - //.fill(theme.middle.base.default.background) - .child(Story::title_for::<_, ContextMenu>(cx)) - .child(Story::label(cx, "Default")) - .child(ContextMenu::new([ - ContextMenuItem::header("Section header"), - ContextMenuItem::Separator, - ContextMenuItem::entry(Label::new("Some entry")), - ])) - } -} diff --git a/crates/storybook/src/stories/components/facepile.rs b/crates/storybook/src/stories/components/facepile.rs deleted file mode 100644 index bbd08ae984..0000000000 --- a/crates/storybook/src/stories/components/facepile.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{static_players, Facepile}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct FacepileStory {} - -impl FacepileStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let players = static_players(); - - Story::container(cx) - .child(Story::title_for::<_, Facepile>(cx)) - .child(Story::label(cx, "Default")) - .child( - div() - .flex() - .gap_3() - .child(Facepile::new(players.clone().into_iter().take(1))) - .child(Facepile::new(players.clone().into_iter().take(2))) - .child(Facepile::new(players.clone().into_iter().take(3))), - ) - } -} diff --git a/crates/storybook/src/stories/components/keybinding.rs b/crates/storybook/src/stories/components/keybinding.rs deleted file mode 100644 index 1acf59fe3b..0000000000 --- a/crates/storybook/src/stories/components/keybinding.rs +++ /dev/null @@ -1,64 +0,0 @@ -use itertools::Itertools; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Keybinding, ModifierKey, ModifierKeys}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct KeybindingStory {} - -impl KeybindingStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let all_modifier_permutations = ModifierKey::iter().permutations(2); - - Story::container(cx) - .child(Story::title_for::<_, Keybinding>(cx)) - .child(Story::label(cx, "Single Key")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::new())) - .child(Story::label(cx, "Single Key with Modifier")) - .child( - div() - .flex() - .gap_3() - .children(ModifierKey::iter().map(|modifier| { - Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier)) - })), - ) - .child(Story::label(cx, "Single Key with Modifier (Permuted)")) - .child( - div().flex().flex_col().children( - all_modifier_permutations - .chunks(4) - .into_iter() - .map(|chunk| { - div() - .flex() - .gap_4() - .py_3() - .children(chunk.map(|permutation| { - let mut modifiers = ModifierKeys::new(); - - for modifier in permutation { - modifiers = modifiers.add(modifier); - } - - Keybinding::new("X".to_string(), modifiers) - })) - }), - ), - ) - .child(Story::label(cx, "Single Key with All Modifiers")) - .child(Keybinding::new("Z".to_string(), ModifierKeys::all())) - .child(Story::label(cx, "Chord")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new()), - ("Z".to_string(), ModifierKeys::new()), - )) - .child(Story::label(cx, "Chord with Modifier")) - .child(Keybinding::new_chord( - ("A".to_string(), ModifierKeys::new().control(true)), - ("Z".to_string(), ModifierKeys::new().shift(true)), - )) - } -} diff --git a/crates/storybook/src/stories/components/language_selector.rs b/crates/storybook/src/stories/components/language_selector.rs deleted file mode 100644 index c6dbd13d3f..0000000000 --- a/crates/storybook/src/stories/components/language_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::LanguageSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LanguageSelectorStory {} - -impl LanguageSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, LanguageSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(LanguageSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/multi_buffer.rs b/crates/storybook/src/stories/components/multi_buffer.rs deleted file mode 100644 index cd760c54dc..0000000000 --- a/crates/storybook/src/stories/components/multi_buffer.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ui::prelude::*; -use ui::{hello_world_rust_buffer_example, MultiBuffer}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct MultiBufferStory {} - -impl MultiBufferStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - Story::container(cx) - .child(Story::title_for::<_, MultiBuffer>(cx)) - .child(Story::label(cx, "Default")) - .child(MultiBuffer::new(vec![ - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - ])) - } -} diff --git a/crates/storybook/src/stories/components/palette.rs b/crates/storybook/src/stories/components/palette.rs deleted file mode 100644 index d14fac6697..0000000000 --- a/crates/storybook/src/stories/components/palette.rs +++ /dev/null @@ -1,53 +0,0 @@ -use ui::prelude::*; -use ui::{Keybinding, ModifierKeys, Palette, PaletteItem}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PaletteStory {} - -impl PaletteStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Palette>(cx)) - .child(Story::label(cx, "Default")) - .child(Palette::new(ScrollState::default())) - .child(Story::label(cx, "With Items")) - .child( - Palette::new(ScrollState::default()) - .placeholder("Execute a command...") - .items(vec![ - PaletteItem::new("theme selector: toggle").keybinding( - Keybinding::new_chord( - ("k".to_string(), ModifierKeys::new().command(true)), - ("t".to_string(), ModifierKeys::new().command(true)), - ), - ), - PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new( - "enter".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new( - ">".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new( - "?".to_string(), - ModifierKeys::new().command(true), - )), - PaletteItem::new("auto update: check"), - PaletteItem::new("auto update: view release notes"), - PaletteItem::new("branches: open recent").keybinding(Keybinding::new( - "b".to_string(), - ModifierKeys::new().command(true).alt(true), - )), - PaletteItem::new("chat panel: toggle focus"), - PaletteItem::new("cli: install"), - PaletteItem::new("client: sign in"), - PaletteItem::new("client: sign out"), - PaletteItem::new("editor: cancel") - .keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())), - ]), - ) - } -} diff --git a/crates/storybook/src/stories/components/panel.rs b/crates/storybook/src/stories/components/panel.rs deleted file mode 100644 index 39a5ceafa2..0000000000 --- a/crates/storybook/src/stories/components/panel.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ui::prelude::*; -use ui::{Label, Panel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct PanelStory {} - -impl PanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Panel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| { - vec![div() - .overflow_y_scroll(ScrollState::default()) - .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))) - .into_any()] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/project_panel.rs b/crates/storybook/src/stories/components/project_panel.rs deleted file mode 100644 index cba71cd21a..0000000000 --- a/crates/storybook/src/stories/components/project_panel.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ui::prelude::*; -use ui::{Panel, ProjectPanel}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ProjectPanelStory {} - -impl ProjectPanelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ProjectPanel>(cx)) - .child(Story::label(cx, "Default")) - .child(Panel::new( - ScrollState::default(), - |_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/recent_projects.rs b/crates/storybook/src/stories/components/recent_projects.rs deleted file mode 100644 index f924654695..0000000000 --- a/crates/storybook/src/stories/components/recent_projects.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::RecentProjects; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct RecentProjectsStory {} - -impl RecentProjectsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, RecentProjects>(cx)) - .child(Story::label(cx, "Default")) - .child(RecentProjects::new()) - } -} diff --git a/crates/storybook/src/stories/components/status_bar.rs b/crates/storybook/src/stories/components/status_bar.rs deleted file mode 100644 index ed3d047c6d..0000000000 --- a/crates/storybook/src/stories/components/status_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::StatusBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct StatusBarStory {} - -impl StatusBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, StatusBar>(cx)) - .child(Story::label(cx, "Default")) - .child(StatusBar::new()) - } -} diff --git a/crates/storybook/src/stories/components/tab.rs b/crates/storybook/src/stories/components/tab.rs deleted file mode 100644 index 6a154ce644..0000000000 --- a/crates/storybook/src/stories/components/tab.rs +++ /dev/null @@ -1,91 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Tab}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabStory {} - -impl TabStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let git_statuses = GitStatus::iter(); - let fs_statuses = FileSystemStatus::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Tab>(cx)) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Default")) - .child(Tab::new()), - ), - ) - .child( - h_stack().child( - v_stack().gap_2().child(Story::label(cx, "Current")).child( - h_stack() - .gap_4() - .child(Tab::new().title("Current".to_string()).current(true)) - .child(Tab::new().title("Not Current".to_string()).current(false)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Titled")) - .child(Tab::new().title("label".to_string())), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "With Icon")) - .child( - Tab::new() - .title("label".to_string()) - .icon(Some(ui::Icon::Envelope)), - ), - ), - ) - .child( - h_stack().child( - v_stack() - .gap_2() - .child(Story::label(cx, "Close Side")) - .child( - h_stack() - .gap_4() - .child( - Tab::new() - .title("Left".to_string()) - .close_side(IconSide::Left), - ) - .child(Tab::new().title("Right".to_string())), - ), - ), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "Git Status")) - .child(h_stack().gap_4().children(git_statuses.map(|git_status| { - Tab::new() - .title(git_status.to_string()) - .git_status(git_status) - }))), - ) - .child( - v_stack() - .gap_2() - .child(Story::label(cx, "File System Status")) - .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| { - Tab::new().title(fs_status.to_string()).fs_status(fs_status) - }))), - ) - } -} diff --git a/crates/storybook/src/stories/components/tab_bar.rs b/crates/storybook/src/stories/components/tab_bar.rs deleted file mode 100644 index b5fa45dfd6..0000000000 --- a/crates/storybook/src/stories/components/tab_bar.rs +++ /dev/null @@ -1,46 +0,0 @@ -use ui::prelude::*; -use ui::{Tab, TabBar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TabBarStory {} - -impl TabBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TabBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TabBar::new(vec![ - Tab::new() - .title("Cargo.toml".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .current(false) - .git_status(GitStatus::Created), - Tab::new().title("theme.rs".to_string()).current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .current(false), - ])) - } -} diff --git a/crates/storybook/src/stories/components/terminal.rs b/crates/storybook/src/stories/components/terminal.rs deleted file mode 100644 index 2bce2e27e5..0000000000 --- a/crates/storybook/src/stories/components/terminal.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Terminal; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TerminalStory {} - -impl TerminalStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Terminal>(cx)) - .child(Story::label(cx, "Default")) - .child(Terminal::new()) - } -} diff --git a/crates/storybook/src/stories/components/theme_selector.rs b/crates/storybook/src/stories/components/theme_selector.rs deleted file mode 100644 index 43e2a704e7..0000000000 --- a/crates/storybook/src/stories/components/theme_selector.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::ThemeSelector; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ThemeSelectorStory {} - -impl ThemeSelectorStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, ThemeSelector>(cx)) - .child(Story::label(cx, "Default")) - .child(ThemeSelector::new()) - } -} diff --git a/crates/storybook/src/stories/components/title_bar.rs b/crates/storybook/src/stories/components/title_bar.rs deleted file mode 100644 index 3c4682b3ba..0000000000 --- a/crates/storybook/src/stories/components/title_bar.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::TitleBar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TitleBarStory {} - -impl TitleBarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TitleBar>(cx)) - .child(Story::label(cx, "Default")) - .child(TitleBar::new(cx)) - } -} diff --git a/crates/storybook/src/stories/components/toolbar.rs b/crates/storybook/src/stories/components/toolbar.rs deleted file mode 100644 index 1413c463b4..0000000000 --- a/crates/storybook/src/stories/components/toolbar.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; - -use ui::prelude::*; -use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ToolbarStory {} - -impl ToolbarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct LeftItemsPayload { - pub theme: Arc, - } - - Story::container(cx) - .child(Story::title_for::<_, Toolbar>(cx)) - .child(Story::label(cx, "Default")) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - let theme = payload.theme.clone(); - - vec![Breadcrumb::new( - PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(), - vec![ - Symbol(vec![ - HighlightedText { - text: "impl ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "ToolbarStory".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "render".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ]), - ], - ) - .into_any()] - }, - Box::new(LeftItemsPayload { - theme: theme.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - } -} diff --git a/crates/storybook/src/stories/components/traffic_lights.rs b/crates/storybook/src/stories/components/traffic_lights.rs deleted file mode 100644 index 3b759a43a3..0000000000 --- a/crates/storybook/src/stories/components/traffic_lights.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::TrafficLights; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct TrafficLightsStory {} - -impl TrafficLightsStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, TrafficLights>(cx)) - .child(Story::label(cx, "Default")) - .child(TrafficLights::new()) - .child(Story::label(cx, "Unfocused")) - .child(TrafficLights::new().window_has_focus(false)) - } -} diff --git a/crates/storybook/src/stories/elements.rs b/crates/storybook/src/stories/elements.rs deleted file mode 100644 index f7afec4d88..0000000000 --- a/crates/storybook/src/stories/elements.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod avatar; -pub mod button; -pub mod icon; -pub mod input; -pub mod label; diff --git a/crates/storybook/src/stories/elements/avatar.rs b/crates/storybook/src/stories/elements/avatar.rs deleted file mode 100644 index a277fa6a1e..0000000000 --- a/crates/storybook/src/stories/elements/avatar.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ui::prelude::*; -use ui::Avatar; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct AvatarStory {} - -impl AvatarStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Avatar>(cx)) - .child(Story::label(cx, "Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", - )) - .child(Story::label(cx, "Rounded rectangle")) - .child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ) - } -} diff --git a/crates/storybook/src/stories/elements/button.rs b/crates/storybook/src/stories/elements/button.rs deleted file mode 100644 index 7ff3fdd30c..0000000000 --- a/crates/storybook/src/stories/elements/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -use gpui2::elements::div; -use gpui2::geometry::rems; -use gpui2::{Element, IntoElement, ViewContext}; -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Icon, IconPosition, Label}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct ButtonStory {} - -impl ButtonStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let states = InteractionState::iter(); - - Story::container(cx) - .child(Story::title_for::<_, Button>(cx)) - .child( - div() - .flex() - .gap_8() - .child( - div() - .child(Story::label(cx, "Ghost (Default)")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Ghost – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Filled")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Left Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .state(state), - ) - }))) - .child(Story::label(cx, "Filled – Right Button")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .state(state), - ) - }))), - ) - .child( - div() - .child(Story::label(cx, "Fixed With")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Left Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Left) - .width(Some(rems(6.).into())), - ) - }))) - .child(Story::label(cx, "Fixed With – Right Icon")) - .child(h_stack().gap_2().children(states.clone().map(|state| { - v_stack() - .gap_1() - .child( - Label::new(state.to_string()) - .color(ui::LabelColor::Muted) - .size(ui::LabelSize::Small), - ) - .child( - Button::new("Label") - .variant(ButtonVariant::Filled) - .state(state) - .icon(Icon::Plus) - .icon_position(IconPosition::Right) - .width(Some(rems(6.).into())), - ) - }))), - ), - ) - .child(Story::label(cx, "Button with `on_click`")) - .child( - Button::new("Label") - .variant(ButtonVariant::Ghost) - // NOTE: There currently appears to be a bug in GPUI2 where only the last event handler will fire. - // So adding additional buttons with `on_click`s after this one will cause this `on_click` to not fire. - .on_click(|_view, _cx| println!("Button clicked.")), - ) - } -} diff --git a/crates/storybook/src/stories/elements/icon.rs b/crates/storybook/src/stories/elements/icon.rs deleted file mode 100644 index 66d3abc0b3..0000000000 --- a/crates/storybook/src/stories/elements/icon.rs +++ /dev/null @@ -1,19 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; -use ui::{Icon, IconElement}; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct IconStory {} - -impl IconStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); - - Story::container(cx) - .child(Story::title_for::<_, IconElement>(cx)) - .child(Story::label(cx, "All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) - } -} diff --git a/crates/storybook/src/stories/elements/input.rs b/crates/storybook/src/stories/elements/input.rs deleted file mode 100644 index 8617b7daaf..0000000000 --- a/crates/storybook/src/stories/elements/input.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ui::prelude::*; -use ui::Input; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct InputStory {} - -impl InputStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Input>(cx)) - .child(Story::label(cx, "Default")) - .child(div().flex().child(Input::new("Search"))) - } -} diff --git a/crates/storybook/src/stories/elements/label.rs b/crates/storybook/src/stories/elements/label.rs deleted file mode 100644 index 1b63cb3a3a..0000000000 --- a/crates/storybook/src/stories/elements/label.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ui::prelude::*; -use ui::Label; - -use crate::story::Story; - -#[derive(Element, Default)] -pub struct LabelStory {} - -impl LabelStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - Story::container(cx) - .child(Story::title_for::<_, Label>(cx)) - .child(Story::label(cx, "Default")) - .child(Label::new("Hello, world!")) - .child(Story::label(cx, "Highlighted")) - .child(Label::new("Hello, world!").with_highlights(vec![0, 1, 2, 7, 8, 12])) - } -} diff --git a/crates/storybook/src/stories/kitchen_sink.rs b/crates/storybook/src/stories/kitchen_sink.rs deleted file mode 100644 index ae826f934e..0000000000 --- a/crates/storybook/src/stories/kitchen_sink.rs +++ /dev/null @@ -1,26 +0,0 @@ -use strum::IntoEnumIterator; -use ui::prelude::*; - -use crate::story::Story; -use crate::story_selector::{ComponentStory, ElementStory}; - -#[derive(Element, Default)] -pub struct KitchenSinkStory {} - -impl KitchenSinkStory { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let element_stories = ElementStory::iter().map(|selector| selector.story()); - let component_stories = ComponentStory::iter().map(|selector| selector.story()); - - Story::container(cx) - .overflow_y_scroll(ScrollState::default()) - .child(Story::title(cx, "Kitchen Sink")) - .child(Story::label(cx, "Elements")) - .child(div().flex().flex_col().children_any(element_stories)) - .child(Story::label(cx, "Components")) - .child(div().flex().flex_col().children_any(component_stories)) - // Add a bit of space at the bottom of the kitchen sink so elements - // don't end up squished right up against the bottom of the screen. - .child(div().p_4()) - } -} diff --git a/crates/storybook/src/story.rs b/crates/storybook/src/story.rs deleted file mode 100644 index 16eae50f88..0000000000 --- a/crates/storybook/src/story.rs +++ /dev/null @@ -1,44 +0,0 @@ -use gpui2::elements::div::Div; -use ui::prelude::*; -use ui::theme; - -pub struct Story {} - -impl Story { - pub fn container(cx: &mut ViewContext) -> Div { - let theme = theme(cx); - - div() - .size_full() - .flex() - .flex_col() - .pt_2() - .px_4() - .font("Zed Mono Extended") - .fill(theme.lowest.base.default.background) - } - - pub fn title(cx: &mut ViewContext, title: &str) -> impl Element { - let theme = theme(cx); - - div() - .text_xl() - .text_color(theme.lowest.base.default.foreground) - .child(title.to_owned()) - } - - pub fn title_for(cx: &mut ViewContext) -> impl Element { - Self::title(cx, std::any::type_name::()) - } - - pub fn label(cx: &mut ViewContext, label: &str) -> impl Element { - let theme = theme(cx); - - div() - .mt_4() - .mb_2() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(label.to_owned()) - } -} diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs deleted file mode 100644 index 6b0a9ac78d..0000000000 --- a/crates/storybook/src/story_selector.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::str::FromStr; -use std::sync::OnceLock; - -use anyhow::{anyhow, Context}; -use clap::builder::PossibleValue; -use clap::ValueEnum; -use gpui2::{AnyElement, Element}; -use strum::{EnumIter, EnumString, IntoEnumIterator}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ElementStory { - Avatar, - Button, - Icon, - Input, - Label, -} - -impl ElementStory { - pub fn story(&self) -> AnyElement { - use crate::stories::elements; - - match self { - Self::Avatar => elements::avatar::AvatarStory::default().into_any(), - Self::Button => elements::button::ButtonStory::default().into_any(), - Self::Icon => elements::icon::IconStory::default().into_any(), - Self::Input => elements::input::InputStory::default().into_any(), - Self::Label => elements::label::LabelStory::default().into_any(), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)] -#[strum(serialize_all = "snake_case")] -pub enum ComponentStory { - AssistantPanel, - Breadcrumb, - Buffer, - ContextMenu, - ChatPanel, - CollabPanel, - Facepile, - Keybinding, - LanguageSelector, - MultiBuffer, - Palette, - Panel, - ProjectPanel, - RecentProjects, - StatusBar, - Tab, - TabBar, - Terminal, - ThemeSelector, - TitleBar, - Toolbar, - TrafficLights, -} - -impl ComponentStory { - pub fn story(&self) -> AnyElement { - use crate::stories::components; - - match self { - Self::AssistantPanel => { - components::assistant_panel::AssistantPanelStory::default().into_any() - } - Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::default().into_any(), - Self::Buffer => components::buffer::BufferStory::default().into_any(), - Self::ContextMenu => components::context_menu::ContextMenuStory::default().into_any(), - Self::ChatPanel => components::chat_panel::ChatPanelStory::default().into_any(), - Self::CollabPanel => components::collab_panel::CollabPanelStory::default().into_any(), - Self::Facepile => components::facepile::FacepileStory::default().into_any(), - Self::Keybinding => components::keybinding::KeybindingStory::default().into_any(), - Self::LanguageSelector => { - components::language_selector::LanguageSelectorStory::default().into_any() - } - Self::MultiBuffer => components::multi_buffer::MultiBufferStory::default().into_any(), - Self::Palette => components::palette::PaletteStory::default().into_any(), - Self::Panel => components::panel::PanelStory::default().into_any(), - Self::ProjectPanel => { - components::project_panel::ProjectPanelStory::default().into_any() - } - Self::RecentProjects => { - components::recent_projects::RecentProjectsStory::default().into_any() - } - Self::StatusBar => components::status_bar::StatusBarStory::default().into_any(), - Self::Tab => components::tab::TabStory::default().into_any(), - Self::TabBar => components::tab_bar::TabBarStory::default().into_any(), - Self::Terminal => components::terminal::TerminalStory::default().into_any(), - Self::ThemeSelector => { - components::theme_selector::ThemeSelectorStory::default().into_any() - } - Self::TitleBar => components::title_bar::TitleBarStory::default().into_any(), - Self::Toolbar => components::toolbar::ToolbarStory::default().into_any(), - Self::TrafficLights => { - components::traffic_lights::TrafficLightsStory::default().into_any() - } - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum StorySelector { - Element(ElementStory), - Component(ComponentStory), - KitchenSink, -} - -impl FromStr for StorySelector { - type Err = anyhow::Error; - - fn from_str(raw_story_name: &str) -> std::result::Result { - let story = raw_story_name.to_ascii_lowercase(); - - if story == "kitchen_sink" { - return Ok(Self::KitchenSink); - } - - if let Some((_, story)) = story.split_once("elements/") { - let element_story = ElementStory::from_str(story) - .with_context(|| format!("story not found for element '{story}'"))?; - - return Ok(Self::Element(element_story)); - } - - if let Some((_, story)) = story.split_once("components/") { - let component_story = ComponentStory::from_str(story) - .with_context(|| format!("story not found for component '{story}'"))?; - - return Ok(Self::Component(component_story)); - } - - Err(anyhow!("story not found for '{raw_story_name}'")) - } -} - -impl StorySelector { - pub fn story(&self) -> AnyElement { - match self { - Self::Element(element_story) => element_story.story(), - Self::Component(component_story) => component_story.story(), - Self::KitchenSink => { - crate::stories::kitchen_sink::KitchenSinkStory::default().into_any() - } - } - } -} - -/// The list of all stories available in the storybook. -static ALL_STORY_SELECTORS: OnceLock> = OnceLock::new(); - -impl ValueEnum for StorySelector { - fn value_variants<'a>() -> &'a [Self] { - let stories = ALL_STORY_SELECTORS.get_or_init(|| { - let element_stories = ElementStory::iter().map(StorySelector::Element); - let component_stories = ComponentStory::iter().map(StorySelector::Component); - - element_stories - .chain(component_stories) - .chain(std::iter::once(StorySelector::KitchenSink)) - .collect::>() - }); - - stories - } - - fn to_possible_value(&self) -> Option { - let value = match self { - Self::Element(story) => format!("elements/{story}"), - Self::Component(story) => format!("components/{story}"), - Self::KitchenSink => "kitchen_sink".to_string(), - }; - - Some(PossibleValue::new(value)) - } -} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs deleted file mode 100644 index afae0d5ebe..0000000000 --- a/crates/storybook/src/storybook.rs +++ /dev/null @@ -1,198 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod stories; -mod story; -mod story_selector; - -use std::{process::Command, sync::Arc}; - -use ::theme as legacy_theme; -use clap::Parser; -use gpui2::{ - serde_json, vec2f, view, Element, IntoElement, ParentElement, RectF, ViewContext, WindowBounds, -}; -use legacy_theme::{ThemeRegistry, ThemeSettings}; -use log::LevelFilter; -use settings::{default_settings, SettingsStore}; -use simplelog::SimpleLogger; -use ui::prelude::*; -use ui::{ElementExt, Theme, WorkspaceElement}; - -use crate::story_selector::StorySelector; - -gpui2::actions! { - storybook, - [ToggleInspector] -} - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - #[arg(value_enum)] - story: Option, - - /// The name of the theme to use in the storybook. - /// - /// If not provided, the default theme will be used. - #[arg(long)] - theme: Option, -} - -async fn watch_zed_changes(fs: Arc) -> Option<()> { - if std::env::var("ZED_HOT_RELOAD").is_err() { - return None; - } - use futures::StreamExt; - let mut events = fs - .watch(".".as_ref(), std::time::Duration::from_millis(100)) - .await; - let mut current_child: Option = None; - while let Some(events) = events.next().await { - if !events.iter().any(|event| { - event - .path - .to_str() - .map(|path| path.contains("/crates/")) - .unwrap_or_default() - }) { - continue; - } - let child = current_child.take().map(|mut child| child.kill()); - log::info!("Storybook changed, rebuilding..."); - current_child = Some( - Command::new("cargo") - .args(["run", "-p", "storybook"]) - .spawn() - .ok()?, - ); - } - Some(()) -} - -fn main() { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - let args = Args::parse(); - - let fs = Arc::new(fs::RealFs); - - gpui2::App::new(Assets).unwrap().run(move |cx| { - let mut store = SettingsStore::default(); - store - .set_default_settings(default_settings().as_ref(), cx) - .unwrap(); - cx.set_global(store); - legacy_theme::init(Assets, cx); - // load_embedded_fonts(cx.platform().as_ref()); - - let theme_registry = cx.global::>(); - - let theme_override = args - .theme - .and_then(|theme| { - theme_registry - .list_names(true) - .find(|known_theme| theme == *known_theme) - }) - .and_then(|theme_name| theme_registry.get(&theme_name).ok()); - - cx.spawn(|_| async move { - watch_zed_changes(fs).await; - }) - .detach(); - cx.add_window( - gpui2::WindowOptions { - bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))), - center: true, - ..Default::default() - }, - |cx| match args.story { - Some(selector) => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - div().flex().flex_col().h_full().child_any(selector.story()), - ) - }), - None => view(move |cx| { - render_story( - &mut ViewContext::new(cx), - theme_override.clone(), - WorkspaceElement::default(), - ) - }), - }, - ); - cx.platform().activate(true); - }); -} - -fn render_story>( - cx: &mut ViewContext, - theme_override: Option>, - story: S, -) -> impl Element { - let theme = current_theme(cx, theme_override); - - story.into_element().themed(theme) -} - -fn current_theme( - cx: &mut ViewContext, - theme_override: Option>, -) -> Theme { - let legacy_theme = - theme_override.unwrap_or_else(|| settings::get::(cx).theme.clone()); - - let new_theme: Theme = serde_json::from_value(legacy_theme.base_theme.clone()).unwrap(); - - add_base_theme_to_legacy_theme(&legacy_theme, new_theme) -} - -// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct. -fn add_base_theme_to_legacy_theme(legacy_theme: &legacy_theme::Theme, new_theme: Theme) -> Theme { - legacy_theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| Box::new(new_theme)) - .downcast_ref::() - .unwrap() - .clone() -} - -use anyhow::{anyhow, Result}; -use gpui2::AssetSource; -use rust_embed::RustEmbed; - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "themes/**/*"] -#[include = "fonts/**/*"] -#[include = "icons/**/*"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Vec> { - Self::iter().filter(|p| p.starts_with(path)).collect() - } -} - -// fn load_embedded_fonts(platform: &dyn gpui2::Platform) { -// let font_paths = Assets.list("fonts"); -// let mut embedded_fonts = Vec::new(); -// for font_path in &font_paths { -// if font_path.ends_with(".ttf") { -// let font_path = &*font_path; -// let font_bytes = Assets.load(font_path).unwrap().to_vec(); -// embedded_fonts.push(Arc::from(font_bytes)); -// } -// } -// platform.fonts().add_fonts(&embedded_fonts).unwrap(); -// } diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 7bd9d912a0..0000000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "ui" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -anyhow.workspace = true -chrono = "0.4" -gpui2 = { path = "../gpui2" } -serde.workspace = true -settings = { path = "../settings" } -smallvec.workspace = true -strum = { version = "0.25.0", features = ["derive"] } -theme = { path = "../theme" } -rand = "0.8" diff --git a/crates/ui/docs/_project.md b/crates/ui/docs/_project.md deleted file mode 100644 index a3b72a9d61..0000000000 --- a/crates/ui/docs/_project.md +++ /dev/null @@ -1,13 +0,0 @@ -## Project Plan - -- Port existing UI to GPUI2 -- Update UI in places that GPUI1 was limiting us* -- Understand the needs &/|| struggles the engineers have been having with building UI in the past and address as many of those as possible as we go -- Ship a simple, straightforward system with documentation that is easy to use to build UI - -## Component Classification - -To simplify the understanding of components and minimize unnecessary cognitive load, let's categorize components into two types: - -- An element refers to a standalone component that doesn't import any other 'ui' components. -- A component indicates a component that utilizes or imports other 'ui' components. diff --git a/crates/ui/docs/elevation.md b/crates/ui/docs/elevation.md deleted file mode 100644 index bd34de3396..0000000000 --- a/crates/ui/docs/elevation.md +++ /dev/null @@ -1,57 +0,0 @@ -# Elevation - -Elevation in Zed applies to all surfaces and components. Elevation is categorized into levels. - -Elevation accomplishes the following: -- Allows surfaces to move in front of or behind others, such as content scrolling beneath app top bars. -- Reflects spatial relationships, for instance, how a floating action button’s shadow intimates its disconnection from a collection of cards. -- Directs attention to structures at the highest elevation, like a temporary dialog arising in front of other surfaces. - -Elevations are the initial elevation values assigned to components by default. - -Components may transition to a higher elevation in some cases, like user interations. - -On such occasions, components transition to predetermined dynamic elevation offsets. These are the typical elevations to which components move when they are not at rest. - -## Understanding Elevation - -Elevation can be thought of as the physical closeness of an element to the user. Elements with lower elevations are physically further away from the user on the z-axis and appear to be underneath elements with higher elevations. - -Material Design 3 has a some great visualizations of elevation that may be helpful to understanding the mental modal of elevation. [Material Design – Elevation](https://m3.material.io/styles/elevation/overview) - -## Elevation Levels - -Zed integrates six unique elevation levels in its design system. The elevation of a surface is expressed as a whole number ranging from 0 to 5, both numbers inclusive. A component’s elevation is ascertained by combining the component’s resting elevation with any dynamic elevation offsets. - -The levels are detailed as follows: - -0. App Background -1. UI Surface -2. Elevated Elements -3. Wash -4. Focused Element -5. Dragged Element - -### 0. App Background - -The app background constitutes the lowest elevation layer, appearing behind all other surfaces and components. It is predominantly used for the background color of the app. - -### 1. UI Surface - -The UI Surface is the standard elevation for components and is placed above the app background. It is generally used for the background color of the app bar, card, and sheet. - -### 2. Elevated Elements - -Elevated elements appear above the UI surface layer surfaces and components. Elevated elements are predominantly used for creating popovers, context menus, and tooltips. - -### 3. Wash - -Wash denotes a distinct elevation reserved to isolate app UI layers from high elevation components such as modals, notifications, and overlaid panels. The wash may not consistently be visible when these components are active. This layer is often referred to as a scrim or overlay and the background color of the wash is typically deployed in its design. - -### 4. Focused Element - -Focused elements obtain a higher elevation above surfaces and components at wash elevation. They are often used for modals, notifications, and overlaid panels and indicate that they are the sole element the user is interacting with at the moment. - -### 5. Dragged Element - -Dragged elements gain the highest elevation, thus appearing above surfaces and components at the elevation of focused elements. These are typically used for elements that are being dragged, following the cursor diff --git a/crates/ui/src/children.rs b/crates/ui/src/children.rs deleted file mode 100644 index 947f8d98cf..0000000000 --- a/crates/ui/src/children.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::any::Any; - -use gpui2::{AnyElement, ViewContext}; - -pub type HackyChildren = fn(&mut ViewContext, &dyn Any) -> Vec>; - -pub type HackyChildrenPayload = Box; diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs deleted file mode 100644 index 65b0218565..0000000000 --- a/crates/ui/src/components.rs +++ /dev/null @@ -1,163 +0,0 @@ -mod assistant_panel; -mod breadcrumb; -mod buffer; -mod chat_panel; -mod collab_panel; -mod command_palette; -mod context_menu; -mod editor_pane; -mod facepile; -mod icon_button; -mod keybinding; -mod language_selector; -mod list; -mod multi_buffer; -mod palette; -mod panel; -mod panes; -mod player_stack; -mod project_panel; -mod recent_projects; -mod status_bar; -mod tab; -mod tab_bar; -mod terminal; -mod theme_selector; -mod title_bar; -mod toast; -mod toolbar; -mod traffic_lights; -mod workspace; - -pub use assistant_panel::*; -pub use breadcrumb::*; -pub use buffer::*; -pub use chat_panel::*; -pub use collab_panel::*; -pub use command_palette::*; -pub use context_menu::*; -pub use editor_pane::*; -pub use facepile::*; -pub use icon_button::*; -pub use keybinding::*; -pub use language_selector::*; -pub use list::*; -pub use multi_buffer::*; -pub use palette::*; -pub use panel::*; -pub use panes::*; -pub use player_stack::*; -pub use project_panel::*; -pub use recent_projects::*; -pub use status_bar::*; -pub use tab::*; -pub use tab_bar::*; -pub use terminal::*; -pub use theme_selector::*; -pub use title_bar::*; -pub use toast::*; -pub use toolbar::*; -pub use traffic_lights::*; -pub use workspace::*; - -// Nate: Commenting this out for now, unsure if we need it. - -// use std::marker::PhantomData; -// use std::rc::Rc; - -// use gpui2::elements::div; -// use gpui2::interactive::Interactive; -// use gpui2::platform::MouseButton; -// use gpui2::{ArcCow, Element, EventContext, IntoElement, ParentElement, ViewContext}; - -// struct ButtonHandlers { -// click: Option)>>, -// } - -// impl Default for ButtonHandlers { -// fn default() -> Self { -// Self { click: None } -// } -// } - -// #[derive(Element)] -// pub struct Button { -// handlers: ButtonHandlers, -// label: Option>, -// icon: Option>, -// data: Rc, -// view_type: PhantomData, -// } - -// // Impl block for buttons without data. -// // See below for an impl block for any button. -// impl Button { -// fn new() -> Self { -// Self { -// handlers: ButtonHandlers::default(), -// label: None, -// icon: None, -// data: Rc::new(()), -// view_type: PhantomData, -// } -// } - -// pub fn data(self, data: D) -> Button { -// Button { -// handlers: ButtonHandlers::default(), -// label: self.label, -// icon: self.icon, -// data: Rc::new(data), -// view_type: PhantomData, -// } -// } -// } - -// // Impl block for button regardless of its data type. -// impl Button { -// pub fn label(mut self, label: impl Into>) -> Self { -// self.label = Some(label.into()); -// self -// } - -// pub fn icon(mut self, icon: impl Into>) -> Self { -// self.icon = Some(icon.into()); -// self -// } - -// pub fn on_click( -// mut self, -// handler: impl Fn(&mut V, &D, &mut EventContext) + 'static, -// ) -> Self { -// self.handlers.click = Some(Rc::new(handler)); -// self -// } -// } - -// pub fn button() -> Button { -// Button::new() -// } - -// impl Button { -// fn render( -// &mut self, -// view: &mut V, -// cx: &mut ViewContext, -// ) -> impl IntoElement + Interactive { -// // let colors = &cx.theme::().colors; - -// let button = div() -// // .fill(colors.error(0.5)) -// .h_4() -// .children(self.label.clone()); - -// if let Some(handler) = self.handlers.click.clone() { -// let data = self.data.clone(); -// button.on_mouse_down(MouseButton::Left, move |view, event, cx| { -// handler(view, data.as_ref(), cx) -// }) -// } else { -// button -// } -// } -// } diff --git a/crates/ui/src/components/assistant_panel.rs b/crates/ui/src/components/assistant_panel.rs deleted file mode 100644 index a0a0c52882..0000000000 --- a/crates/ui/src/components/assistant_panel.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::rems; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Label, Panel, PanelSide}; - -#[derive(Element)] -pub struct AssistantPanel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, -} - -impl AssistantPanel { - pub fn new() -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - current_side: PanelSide::default(), - } - } - - pub fn side(mut self, side: PanelSide) -> Self { - self.current_side = side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - struct PanelPayload { - pub scroll_state: ScrollState, - } - - Panel::new( - self.scroll_state.clone(), - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![div() - .flex() - .flex_col() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .gap_2() - .child( - div() - .flex() - .child(IconButton::new(Icon::Menu)) - .child(Label::new("New Conversation")), - ) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::SplitMessage)) - .child(IconButton::new(Icon::Quote)) - .child(IconButton::new(Icon::MagicWand)) - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Maximize)), - ), - ) - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(payload.scroll_state.clone()) - .child(Label::new("Is this thing on?")), - ) - .into_any()] - }, - Box::new(PanelPayload { - scroll_state: self.scroll_state.clone(), - }), - ) - .side(self.current_side) - .width(rems(32.)) - } -} diff --git a/crates/ui/src/components/breadcrumb.rs b/crates/ui/src/components/breadcrumb.rs deleted file mode 100644 index c14e89ee7b..0000000000 --- a/crates/ui/src/components/breadcrumb.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::PathBuf; - -use gpui2::elements::div::Div; - -use crate::{h_stack, theme}; -use crate::{prelude::*, HighlightedText}; - -#[derive(Clone)] -pub struct Symbol(pub Vec); - -#[derive(Element)] -pub struct Breadcrumb { - path: PathBuf, - symbols: Vec, -} - -impl Breadcrumb { - pub fn new(path: PathBuf, symbols: Vec) -> Self { - Self { path, symbols } - } - - fn render_separator(&self, theme: &Theme) -> Div { - div() - .child(" › ") - .text_color(HighlightColor::Default.hsla(theme)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let symbols_len = self.symbols.len(); - - h_stack() - .px_1() - // TODO: Read font from theme (or settings?). - .font("Zed Mono Extended") - .text_sm() - .text_color(theme.middle.base.default.foreground) - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .child(self.path.clone().to_str().unwrap().to_string()) - .child(if !self.symbols.is_empty() { - self.render_separator(&theme) - } else { - div() - }) - .child( - div().flex().children( - self.symbols - .iter() - .enumerate() - // TODO: Could use something like `intersperse` here instead. - .flat_map(|(ix, symbol)| { - let mut items = - vec![div().flex().children(symbol.0.iter().map(|segment| { - div().child(segment.text.clone()).text_color(segment.color) - }))]; - - let is_last_segment = ix == symbols_len - 1; - if !is_last_segment { - items.push(self.render_separator(&theme)); - } - - items - }) - .collect::>(), - ), - ) - } -} diff --git a/crates/ui/src/components/buffer.rs b/crates/ui/src/components/buffer.rs deleted file mode 100644 index 00e5daee55..0000000000 --- a/crates/ui/src/components/buffer.rs +++ /dev/null @@ -1,233 +0,0 @@ -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, v_stack, Icon, IconElement}; - -#[derive(Default, PartialEq, Copy, Clone)] -pub struct PlayerCursor { - color: Hsla, - index: usize, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedText { - pub text: String, - pub color: Hsla, -} - -#[derive(Default, PartialEq, Clone)] -pub struct HighlightedLine { - pub highlighted_texts: Vec, -} - -#[derive(Default, PartialEq, Clone)] -pub struct BufferRow { - pub line_number: usize, - pub code_action: bool, - pub current: bool, - pub line: Option, - pub cursors: Option>, - pub status: GitStatus, - pub show_line_number: bool, -} - -#[derive(Clone)] -pub struct BufferRows { - pub show_line_numbers: bool, - pub rows: Vec, -} - -impl Default for BufferRows { - fn default() -> Self { - Self { - show_line_numbers: true, - rows: vec![BufferRow { - line_number: 1, - code_action: false, - current: true, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - }], - } - } -} - -impl BufferRow { - pub fn new(line_number: usize) -> Self { - Self { - line_number, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number: true, - } - } - - pub fn set_line(mut self, line: Option) -> Self { - self.line = line; - self - } - - pub fn set_cursors(mut self, cursors: Option>) -> Self { - self.cursors = cursors; - self - } - - pub fn add_cursor(mut self, cursor: PlayerCursor) -> Self { - if let Some(cursors) = &mut self.cursors { - cursors.push(cursor); - } else { - self.cursors = Some(vec![cursor]); - } - self - } - - pub fn set_status(mut self, status: GitStatus) -> Self { - self.status = status; - self - } - - pub fn set_show_line_number(mut self, show_line_number: bool) -> Self { - self.show_line_number = show_line_number; - self - } - - pub fn set_code_action(mut self, code_action: bool) -> Self { - self.code_action = code_action; - self - } - - pub fn set_current(mut self, current: bool) -> Self { - self.current = current; - self - } -} - -#[derive(Element, Clone)] -pub struct Buffer { - scroll_state: ScrollState, - rows: Option, - readonly: bool, - language: Option, - title: Option, - path: Option, -} - -impl Buffer { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - rows: Some(BufferRows::default()), - readonly: false, - language: None, - title: Some("untitled".to_string()), - path: None, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - pub fn set_title>>(mut self, title: T) -> Self { - self.title = title.into(); - self - } - - pub fn set_path>>(mut self, path: P) -> Self { - self.path = path.into(); - self - } - - pub fn set_readonly(mut self, readonly: bool) -> Self { - self.readonly = readonly; - self - } - - pub fn set_rows>>(mut self, rows: R) -> Self { - self.rows = rows.into(); - self - } - - pub fn set_language>>(mut self, language: L) -> Self { - self.language = language.into(); - self - } - - fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let line_background = if row.current { - theme.middle.base.default.background - } else { - system_color.transparent - }; - - let line_number_color = if row.current { - HighlightColor::Default.hsla(&theme) - } else { - HighlightColor::Comment.hsla(&theme) - }; - - h_stack() - .fill(line_background) - .w_full() - .gap_2() - .px_1() - .child( - h_stack() - .w_4() - .h_full() - .px_0p5() - .when(row.code_action, |c| { - div().child(IconElement::new(Icon::Bolt)) - }), - ) - .when(row.show_line_number, |this| { - this.child( - h_stack().justify_end().px_0p5().w_3().child( - div() - .text_color(line_number_color) - .child(row.line_number.to_string()), - ), - ) - }) - .child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx))) - .children(row.line.map(|line| { - div() - .flex() - .children(line.highlighted_texts.iter().map(|highlighted_text| { - div() - .text_color(highlighted_text.color) - .child(highlighted_text.text.clone()) - })) - })) - } - - fn render_rows(&self, cx: &WindowContext) -> Vec> { - match &self.rows { - Some(rows) => rows - .rows - .iter() - .map(|row| Self::render_row(row.clone(), cx)) - .collect(), - None => vec![], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let rows = self.render_rows(cx); - v_stack() - .flex_1() - .w_full() - .h_full() - .fill(theme.highest.base.default.background) - .children(rows) - } -} diff --git a/crates/ui/src/components/chat_panel.rs b/crates/ui/src/components/chat_panel.rs deleted file mode 100644 index 5ae66967b6..0000000000 --- a/crates/ui/src/components/chat_panel.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::marker::PhantomData; - -use chrono::NaiveDateTime; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{Icon, IconButton, Input, Label, LabelColor}; - -#[derive(Element)] -pub struct ChatPanel { - view_type: PhantomData, - scroll_state: ScrollState, - messages: Vec, -} - -impl ChatPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - messages: Vec::new(), - } - } - - pub fn with_messages(mut self, messages: Vec) -> Self { - self.messages = messages; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .justify_between() - .h_full() - .px_2() - .gap_2() - // Header - .child( - div() - .flex() - .justify_between() - .py_2() - .child(div().flex().child(Label::new("#design"))) - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::File)) - .child(IconButton::new(Icon::AudioOn)), - ), - ) - .child( - div() - .flex() - .flex_col() - // Chat Body - .child( - div() - .w_full() - .flex() - .flex_col() - .gap_3() - .overflow_y_scroll(self.scroll_state.clone()) - .children(self.messages.clone()), - ) - // Composer - .child(div().flex().my_2().child(Input::new("Message #design"))), - ) - } -} - -#[derive(Element, Clone)] -pub struct ChatMessage { - author: String, - text: String, - sent_at: NaiveDateTime, -} - -impl ChatMessage { - pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { - Self { - author, - text, - sent_at, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .flex_col() - .child( - div() - .flex() - .gap_2() - .child(Label::new(self.author.clone())) - .child( - Label::new(self.sent_at.format("%m/%d/%Y").to_string()) - .color(LabelColor::Muted), - ), - ) - .child(div().child(Label::new(self.text.clone()))) - } -} diff --git a/crates/ui/src/components/collab_panel.rs b/crates/ui/src/components/collab_panel.rs deleted file mode 100644 index 14dd43294f..0000000000 --- a/crates/ui/src/components/collab_panel.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::elements::{img, svg}; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{ - static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, - ListHeader, ToggleState, -}; - -#[derive(Element)] -pub struct CollabPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CollabPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_64() - .h_full() - .fill(theme.middle.base.default.background) - .child( - v_stack() - .w_full() - .overflow_y_scroll(self.scroll_state.clone()) - .child( - div() - .fill(theme.lowest.base.default.background) - .pb_1() - .border_color(theme.lowest.base.default.border) - .border_b() - .child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CRDB") - .left_icon(Icon::Hash.into()) - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_channels()) - .header( - ListHeader::new("CHANNELS").set_toggle(ToggleState::Toggled), - ) - .empty_message("No channels yet. Add a channel to get started.") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – ONLINE") - .set_toggle(ToggleState::Toggled), - ) - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - v_stack().py_1().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CONTACTS – OFFLINE") - .set_toggle(ToggleState::NotToggled), - ) - .set_toggle(ToggleState::NotToggled), - ), - ), - ) - .child( - div() - .h_7() - .px_2() - .border_t() - .border_color(theme.middle.variant.default.border) - .flex() - .items_center() - .child( - div() - .text_sm() - .text_color(theme.middle.variant.default.foreground) - .child("Find..."), - ), - ) - } - - fn list_section_header( - &self, - label: impl Into>, - expanded: bool, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .justify_between() - .items_center() - .child(div().flex().gap_1().text_sm().child(label)) - .child( - div().flex().h_full().gap_1().items_center().child( - svg() - .path(if expanded { - "icons/caret_down.svg" - } else { - "icons/caret_up.svg" - }) - .w_3p5() - .h_3p5() - .fill(theme.middle.variant.default.foreground), - ), - ) - } - - fn list_item( - &self, - avatar_uri: impl Into>, - label: impl Into>, - theme: &Theme, - ) -> impl Element { - div() - .h_7() - .px_2() - .flex() - .items_center() - .hover() - .fill(theme.lowest.variant.hovered.background) - .active() - .fill(theme.lowest.variant.pressed.background) - .child( - div() - .flex() - .items_center() - .gap_1() - .text_sm() - .child( - img() - .uri(avatar_uri) - .size_3p5() - .rounded_full() - .fill(theme.middle.positive.default.foreground), - ) - .child(label), - ) - } -} diff --git a/crates/ui/src/components/command_palette.rs b/crates/ui/src/components/command_palette.rs deleted file mode 100644 index 797876c05e..0000000000 --- a/crates/ui/src/components/command_palette.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{example_editor_actions, OrderMethod, Palette}; - -#[derive(Element)] -pub struct CommandPalette { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl CommandPalette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(example_editor_actions()) - .placeholder("Execute a command...") - .empty_string("No items found.") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs deleted file mode 100644 index 0808bf6556..0000000000 --- a/crates/ui/src/components/context_menu.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::theme::theme; -use crate::{ - v_stack, Label, List, ListEntry, ListItem, ListItemVariant, ListSeparator, ListSubHeader, -}; - -#[derive(Clone)] -pub enum ContextMenuItem { - Header(&'static str), - Entry(Label), - Separator, -} - -impl ContextMenuItem { - fn to_list_item(self) -> ListItem { - match self { - ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), - ContextMenuItem::Entry(label) => { - ListEntry::new(label).variant(ListItemVariant::Inset).into() - } - ContextMenuItem::Separator => ListSeparator::new().into(), - } - } - pub fn header(label: &'static str) -> Self { - Self::Header(label) - } - pub fn separator() -> Self { - Self::Separator - } - pub fn entry(label: Label) -> Self { - Self::Entry(label) - } -} - -#[derive(Element)] -pub struct ContextMenu { - items: Vec, -} - -impl ContextMenu { - pub fn new(items: impl IntoIterator) -> Self { - Self { - items: items.into_iter().collect(), - } - } - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - v_stack() - .flex() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - List::new( - self.items - .clone() - .into_iter() - .map(ContextMenuItem::to_list_item) - .collect(), - ) - .set_toggle(ToggleState::Toggled), - ) - //div().p_1().children(self.items.clone()) - } -} diff --git a/crates/ui/src/components/editor_pane.rs b/crates/ui/src/components/editor_pane.rs deleted file mode 100644 index 561081164c..0000000000 --- a/crates/ui/src/components/editor_pane.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::marker::PhantomData; -use std::path::PathBuf; - -use crate::prelude::*; -use crate::{v_stack, Breadcrumb, Buffer, Icon, IconButton, Symbol, Tab, TabBar, Toolbar}; - -pub struct Editor { - pub tabs: Vec, - pub path: PathBuf, - pub symbols: Vec, - pub buffer: Buffer, -} - -#[derive(Element)] -pub struct EditorPane { - view_type: PhantomData, - editor: Editor, -} - -impl EditorPane { - pub fn new(editor: Editor) -> Self { - Self { - view_type: PhantomData, - editor, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - struct LeftItemsPayload { - path: PathBuf, - symbols: Vec, - } - - v_stack() - .w_full() - .h_full() - .flex_1() - .child(TabBar::new(self.editor.tabs.clone())) - .child(Toolbar::new( - |_, payload| { - let payload = payload.downcast_ref::().unwrap(); - - vec![Breadcrumb::new(payload.path.clone(), payload.symbols.clone()).into_any()] - }, - Box::new(LeftItemsPayload { - path: self.editor.path.clone(), - symbols: self.editor.symbols.clone(), - }), - |_, _| { - vec![ - IconButton::new(Icon::InlayHint).into_any(), - IconButton::new(Icon::MagnifyingGlass).into_any(), - IconButton::new(Icon::MagicWand).into_any(), - ] - }, - Box::new(()), - )) - .child(self.editor.buffer.clone()) - } -} diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs deleted file mode 100644 index 949e226c25..0000000000 --- a/crates/ui/src/components/facepile.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Avatar, Player}; - -#[derive(Element)] -pub struct Facepile { - players: Vec, -} - -impl Facepile { - pub fn new>(players: P) -> Self { - Self { - players: players.collect(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let player_count = self.players.len(); - let player_list = self.players.iter().enumerate().map(|(ix, player)| { - let isnt_last = ix < player_count - 1; - - div() - .when(isnt_last, |div| div.neg_mr_1()) - .child(Avatar::new(player.avatar_src().to_string())) - }); - div().p_1().flex().items_center().children(player_list) - } -} diff --git a/crates/ui/src/components/icon_button.rs b/crates/ui/src/components/icon_button.rs deleted file mode 100644 index 8ac122d3eb..0000000000 --- a/crates/ui/src/components/icon_button.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement}; - -#[derive(Element)] -pub struct IconButton { - icon: Icon, - color: IconColor, - variant: ButtonVariant, - state: InteractionState, -} - -impl IconButton { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - variant: ButtonVariant::default(), - state: InteractionState::default(), - } - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = icon; - self - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let icon_color = match (self.state, self.color) { - (InteractionState::Disabled, _) => IconColor::Disabled, - _ => self.color, - }; - - let mut div = div(); - if self.variant == ButtonVariant::Filled { - div = div.fill(theme.highest.on.default.background); - } - - div.w_7() - .h_6() - .flex() - .items_center() - .justify_center() - .rounded_md() - .hover() - .fill(theme.highest.base.hovered.background) - .active() - .fill(theme.highest.base.pressed.background) - .child(IconElement::new(self.icon).color(icon_color)) - } -} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs deleted file mode 100644 index 63c5f11039..0000000000 --- a/crates/ui/src/components/keybinding.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::collections::HashSet; - -use strum::{EnumIter, IntoEnumIterator}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Keybinding { - /// A keybinding consists of a key and a set of modifier keys. - /// More then one keybinding produces a chord. - /// - /// This should always contain at least one element. - keybinding: Vec<(String, ModifierKeys)>, -} - -impl Keybinding { - pub fn new(key: String, modifiers: ModifierKeys) -> Self { - Self { - keybinding: vec![(key, modifiers)], - } - } - - pub fn new_chord( - first_note: (String, ModifierKeys), - second_note: (String, ModifierKeys), - ) -> Self { - Self { - keybinding: vec![first_note, second_note], - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .gap_2() - .children(self.keybinding.iter().map(|(key, modifiers)| { - div() - .flex() - .gap_1() - .children(ModifierKey::iter().filter_map(|modifier| { - if modifiers.0.contains(&modifier) { - Some(Key::new(modifier.glyph())) - } else { - None - } - })) - .child(Key::new(key.clone())) - })) - } -} - -#[derive(Element)] -pub struct Key { - key: String, -} - -impl Key { - pub fn new(key: K) -> Self - where - K: Into, - { - Self { key: key.into() } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .px_2() - .py_0() - .rounded_md() - .text_sm() - .text_color(theme.lowest.on.default.foreground) - .fill(theme.lowest.on.default.background) - .child(self.key.clone()) - } -} - -// NOTE: The order the modifier keys appear in this enum impacts the order in -// which they are rendered in the UI. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum ModifierKey { - Control, - Alt, - Command, - Shift, -} - -impl ModifierKey { - /// Returns the glyph for the [`ModifierKey`]. - pub fn glyph(&self) -> char { - match self { - Self::Control => '^', - Self::Alt => '⌥', - Self::Command => '⌘', - Self::Shift => '⇧', - } - } -} - -#[derive(Clone)] -pub struct ModifierKeys(HashSet); - -impl ModifierKeys { - pub fn new() -> Self { - Self(HashSet::new()) - } - - pub fn all() -> Self { - Self(HashSet::from_iter(ModifierKey::iter())) - } - - pub fn add(mut self, modifier: ModifierKey) -> Self { - self.0.insert(modifier); - self - } - - pub fn control(mut self, control: bool) -> Self { - if control { - self.0.insert(ModifierKey::Control); - } else { - self.0.remove(&ModifierKey::Control); - } - - self - } - - pub fn alt(mut self, alt: bool) -> Self { - if alt { - self.0.insert(ModifierKey::Alt); - } else { - self.0.remove(&ModifierKey::Alt); - } - - self - } - - pub fn command(mut self, command: bool) -> Self { - if command { - self.0.insert(ModifierKey::Command); - } else { - self.0.remove(&ModifierKey::Command); - } - - self - } - - pub fn shift(mut self, shift: bool) -> Self { - if shift { - self.0.insert(ModifierKey::Shift); - } else { - self.0.remove(&ModifierKey::Shift); - } - - self - } -} diff --git a/crates/ui/src/components/language_selector.rs b/crates/ui/src/components/language_selector.rs deleted file mode 100644 index 124d7f13ee..0000000000 --- a/crates/ui/src/components/language_selector.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct LanguageSelector { - scroll_state: ScrollState, -} - -impl LanguageSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("C"), - PaletteItem::new("C++"), - PaletteItem::new("CSS"), - PaletteItem::new("Elixir"), - PaletteItem::new("Elm"), - PaletteItem::new("ERB"), - PaletteItem::new("Rust (current)"), - PaletteItem::new("Scheme"), - PaletteItem::new("TOML"), - PaletteItem::new("TypeScript"), - ]) - .placeholder("Select a language...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/list.rs b/crates/ui/src/components/list.rs deleted file mode 100644 index b7dff6b2c5..0000000000 --- a/crates/ui/src/components/list.rs +++ /dev/null @@ -1,512 +0,0 @@ -use gpui2::elements::div::Div; -use gpui2::{Hsla, WindowContext}; - -use crate::prelude::*; -use crate::{ - h_stack, theme, token, v_stack, Avatar, DisclosureControlVisibility, Icon, IconColor, - IconElement, IconSize, InteractionState, Label, LabelColor, LabelSize, SystemColor, - ToggleState, -}; - -#[derive(Clone, Copy, Default, Debug, PartialEq)] -pub enum ListItemVariant { - /// The list item extends to the far left and right of the list. - #[default] - FullWidth, - Inset, -} - -#[derive(Element, Clone, Copy)] -pub struct ListHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, - state: InteractionState, - toggleable: Toggleable, -} - -impl ListHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - state: InteractionState::default(), - toggleable: Toggleable::default(), - } - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self { - self.toggleable = toggleable; - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - fn disclosure_control(&self) -> Div { - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - match (is_toggleable, is_toggled) { - (false, _) => div(), - (_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)), - (_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)), - } - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = self.disclosure_control(); - - h_stack() - .flex_1() - .w_full() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ) - .child(disclosure_control), - ) - } -} - -#[derive(Element, Clone, Copy)] -pub struct ListSubHeader { - label: &'static str, - left_icon: Option, - variant: ListItemVariant, -} - -impl ListSubHeader { - pub fn new(label: &'static str) -> Self { - Self { - label, - left_icon: None, - variant: ListItemVariant::default(), - } - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - h_stack().flex_1().w_full().relative().py_1().child( - div() - .h_6() - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - .flex() - .flex_1() - .w_full() - .gap_1() - .items_center() - .justify_between() - .child( - div() - .flex() - .gap_1() - .items_center() - .children(self.left_icon.map(|i| { - IconElement::new(i) - .color(IconColor::Muted) - .size(IconSize::Small) - })) - .child( - Label::new(self.label) - .color(LabelColor::Muted) - .size(LabelSize::Small), - ), - ), - ) - } -} - -#[derive(Clone)] -pub enum LeftContent { - Icon(Icon), - Avatar(&'static str), -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum ListEntrySize { - #[default] - Small, - Medium, -} - -#[derive(Clone, Element)] -pub enum ListItem { - Entry(ListEntry), - Separator(ListSeparator), - Header(ListSubHeader), -} - -impl From for ListItem { - fn from(entry: ListEntry) -> Self { - Self::Entry(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSeparator) -> Self { - Self::Separator(entry) - } -} - -impl From for ListItem { - fn from(entry: ListSubHeader) -> Self { - Self::Header(entry) - } -} - -impl ListItem { - fn render(&mut self, v: &mut V, cx: &mut ViewContext) -> impl IntoElement { - match self { - ListItem::Entry(entry) => div().child(entry.render(v, cx)), - ListItem::Separator(separator) => div().child(separator.render(v, cx)), - ListItem::Header(header) => div().child(header.render(v, cx)), - } - } - pub fn new(label: Label) -> Self { - Self::Entry(ListEntry::new(label)) - } - pub fn as_entry(&mut self) -> Option<&mut ListEntry> { - if let Self::Entry(entry) = self { - Some(entry) - } else { - None - } - } -} - -#[derive(Element, Clone)] -pub struct ListEntry { - disclosure_control_style: DisclosureControlVisibility, - indent_level: u32, - label: Label, - left_content: Option, - variant: ListItemVariant, - size: ListEntrySize, - state: InteractionState, - toggle: Option, -} - -impl ListEntry { - pub fn new(label: Label) -> Self { - Self { - disclosure_control_style: DisclosureControlVisibility::default(), - indent_level: 0, - label, - variant: ListItemVariant::default(), - left_content: None, - size: ListEntrySize::default(), - state: InteractionState::default(), - toggle: None, - } - } - pub fn variant(mut self, variant: ListItemVariant) -> Self { - self.variant = variant; - self - } - pub fn indent_level(mut self, indent_level: u32) -> Self { - self.indent_level = indent_level; - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggle = Some(toggle); - self - } - - pub fn left_content(mut self, left_content: LeftContent) -> Self { - self.left_content = Some(left_content); - self - } - - pub fn left_icon(mut self, left_icon: Icon) -> Self { - self.left_content = Some(LeftContent::Icon(left_icon)); - self - } - - pub fn left_avatar(mut self, left_avatar: &'static str) -> Self { - self.left_content = Some(LeftContent::Avatar(left_avatar)); - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn size(mut self, size: ListEntrySize) -> Self { - self.size = size; - self - } - - pub fn disclosure_control_style( - mut self, - disclosure_control_style: DisclosureControlVisibility, - ) -> Self { - self.disclosure_control_style = disclosure_control_style; - self - } - - fn background_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Hovered => theme.lowest.base.hovered.background, - InteractionState::Active => theme.lowest.base.pressed.background, - InteractionState::Enabled => theme.lowest.on.default.background, - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn disclosure_control( - &mut self, - cx: &mut ViewContext, - ) -> Option> { - let theme = theme(cx); - let token = token(); - - let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle { - IconElement::new(Icon::ChevronDown) - } else { - IconElement::new(Icon::ChevronRight) - } - .color(IconColor::Muted) - .size(IconSize::Small); - - match (self.toggle, self.disclosure_control_style) { - (Some(_), DisclosureControlVisibility::OnHover) => { - Some(div().absolute().neg_left_5().child(disclosure_control_icon)) - } - (Some(_), DisclosureControlVisibility::Always) => { - Some(div().child(disclosure_control_icon)) - } - (None, _) => None, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let system_color = SystemColor::new(); - let background_color = self.background_color(cx); - - let left_content = match self.left_content { - Some(LeftContent::Icon(i)) => { - Some(h_stack().child(IconElement::new(i).size(IconSize::Small))) - } - Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), - None => None, - }; - - let sized_item = match self.size { - ListEntrySize::Small => div().h_6(), - ListEntrySize::Medium => div().h_7(), - }; - - div() - .fill(background_color) - .when(self.state == InteractionState::Focused, |this| { - this.border() - .border_color(theme.lowest.accent.default.border) - }) - .relative() - .py_1() - .child( - sized_item - .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) - // .ml(rems(0.75 * self.indent_level as f32)) - .children((0..self.indent_level).map(|_| { - div() - .w(token.list_indent_depth) - .h_full() - .flex() - .justify_center() - .child(h_stack().child(div().w_px().h_full()).child( - div().w_px().h_full().fill(theme.middle.base.default.border), - )) - })) - .flex() - .gap_1() - .items_center() - .relative() - .children(self.disclosure_control(cx)) - .children(left_content) - .child(self.label.clone()), - ) - } -} - -#[derive(Clone, Default, Element)] -pub struct ListSeparator; - -impl ListSeparator { - pub fn new() -> Self { - Self::default() - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().h_px().w_full().fill(theme.lowest.base.default.border) - } -} - -#[derive(Element)] -pub struct List { - items: Vec, - empty_message: &'static str, - header: Option, - toggleable: Toggleable, -} - -impl List { - pub fn new(items: Vec) -> Self { - Self { - items, - empty_message: "No items", - header: None, - toggleable: Toggleable::default(), - } - } - - pub fn empty_message(mut self, empty_message: &'static str) -> Self { - self.empty_message = empty_message; - self - } - - pub fn header(mut self, header: ListHeader) -> Self { - self.header = Some(header); - self - } - - pub fn set_toggle(mut self, toggle: ToggleState) -> Self { - self.toggleable = toggle.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - let is_toggleable = self.toggleable != Toggleable::NotToggleable; - let is_toggled = Toggleable::is_toggled(&self.toggleable); - - let disclosure_control = if is_toggleable { - IconElement::new(Icon::ChevronRight) - } else { - IconElement::new(Icon::ChevronDown) - }; - - let list_content = match (self.items.is_empty(), is_toggled) { - (_, false) => div(), - (false, _) => div().children(self.items.iter().cloned()), - (true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)), - }; - - v_stack() - .py_1() - .children( - self.header - .clone() - .map(|header| header.set_toggleable(self.toggleable)), - ) - .child(list_content) - } -} diff --git a/crates/ui/src/components/multi_buffer.rs b/crates/ui/src/components/multi_buffer.rs deleted file mode 100644 index d38603457a..0000000000 --- a/crates/ui/src/components/multi_buffer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{v_stack, Buffer, Icon, IconButton, Label, LabelSize}; - -#[derive(Element)] -pub struct MultiBuffer { - view_type: PhantomData, - buffers: Vec, -} - -impl MultiBuffer { - pub fn new(buffers: Vec) -> Self { - Self { - view_type: PhantomData, - buffers, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_full() - .h_full() - .flex_1() - .children(self.buffers.clone().into_iter().map(|buffer| { - v_stack() - .child( - div() - .flex() - .items_center() - .justify_between() - .p_4() - .fill(theme.lowest.base.default.background) - .child(Label::new("main.rs").size(LabelSize::Small)) - .child(IconButton::new(Icon::ArrowUpRight)), - ) - .child(buffer) - })) - } -} diff --git a/crates/ui/src/components/palette.rs b/crates/ui/src/components/palette.rs deleted file mode 100644 index 16001e50c1..0000000000 --- a/crates/ui/src/components/palette.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::theme; -use crate::{h_stack, v_stack, Keybinding, Label, LabelColor}; - -#[derive(Element)] -pub struct Palette { - view_type: PhantomData, - scroll_state: ScrollState, - input_placeholder: &'static str, - empty_string: &'static str, - items: Vec, - default_order: OrderMethod, -} - -impl Palette { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - input_placeholder: "Find something...", - empty_string: "No items found.", - items: vec![], - default_order: OrderMethod::default(), - } - } - - pub fn items(mut self, items: Vec) -> Self { - self.items = items; - self - } - - pub fn placeholder(mut self, input_placeholder: &'static str) -> Self { - self.input_placeholder = input_placeholder; - self - } - - pub fn empty_string(mut self, empty_string: &'static str) -> Self { - self.empty_string = empty_string; - self - } - - // TODO: Hook up sort order - pub fn default_order(mut self, default_order: OrderMethod) -> Self { - self.default_order = default_order; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - v_stack() - .w_96() - .rounded_lg() - .fill(theme.lowest.base.default.background) - .border() - .border_color(theme.lowest.base.default.border) - .child( - v_stack() - .gap_px() - .child(v_stack().py_0p5().px_1().child( - div().px_2().py_0p5().child( - Label::new(self.input_placeholder).color(LabelColor::Placeholder), - ), - )) - .child(div().h_px().w_full().fill(theme.lowest.base.default.border)) - .child( - v_stack() - .py_0p5() - .px_1() - .grow() - .max_h_96() - .overflow_y_scroll(self.scroll_state.clone()) - .children( - vec![if self.items.is_empty() { - Some(h_stack().justify_between().px_2().py_1().child( - Label::new(self.empty_string).color(LabelColor::Muted), - )) - } else { - None - }] - .into_iter() - .flatten(), - ) - .children(self.items.iter().map(|item| { - h_stack() - .justify_between() - .px_2() - .py_0p5() - .rounded_lg() - .hover() - .fill(theme.lowest.base.hovered.background) - .active() - .fill(theme.lowest.base.pressed.background) - .child(item.clone()) - })), - ), - ) - } -} - -#[derive(Element, Clone)] -pub struct PaletteItem { - pub label: &'static str, - pub sublabel: Option<&'static str>, - pub keybinding: Option, -} - -impl PaletteItem { - pub fn new(label: &'static str) -> Self { - Self { - label, - sublabel: None, - keybinding: None, - } - } - - pub fn label(mut self, label: &'static str) -> Self { - self.label = label; - self - } - - pub fn sublabel>>(mut self, sublabel: L) -> Self { - self.sublabel = sublabel.into(); - self - } - - pub fn keybinding(mut self, keybinding: K) -> Self - where - K: Into>, - { - self.keybinding = keybinding.into(); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_row() - .grow() - .justify_between() - .child( - v_stack() - .child(Label::new(self.label)) - .children(self.sublabel.map(|sublabel| Label::new(sublabel))), - ) - .children(self.keybinding.clone()) - } -} diff --git a/crates/ui/src/components/panel.rs b/crates/ui/src/components/panel.rs deleted file mode 100644 index cbcf502670..0000000000 --- a/crates/ui/src/components/panel.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::AbsoluteLength; - -use crate::prelude::*; -use crate::{theme, token, v_stack}; - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelAllowedSides { - LeftOnly, - RightOnly, - BottomOnly, - #[default] - LeftAndRight, - All, -} - -impl PanelAllowedSides { - /// Return a `HashSet` that contains the allowable `PanelSide`s. - pub fn allowed_sides(&self) -> HashSet { - match self { - Self::LeftOnly => HashSet::from_iter([PanelSide::Left]), - Self::RightOnly => HashSet::from_iter([PanelSide::Right]), - Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]), - Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]), - Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]), - } - } -} - -#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)] -pub enum PanelSide { - #[default] - Left, - Right, - Bottom, -} - -use std::collections::HashSet; - -#[derive(Element)] -pub struct Panel { - view_type: PhantomData, - scroll_state: ScrollState, - current_side: PanelSide, - /// Defaults to PanelAllowedSides::LeftAndRight - allowed_sides: PanelAllowedSides, - initial_width: AbsoluteLength, - width: Option, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Panel { - pub fn new( - scroll_state: ScrollState, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - let token = token(); - - Self { - view_type: PhantomData, - scroll_state, - current_side: PanelSide::default(), - allowed_sides: PanelAllowedSides::default(), - initial_width: token.default_panel_size, - width: None, - children, - payload, - } - } - - pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self { - self.initial_width = initial_width; - self - } - - pub fn width(mut self, width: AbsoluteLength) -> Self { - self.width = Some(width); - self - } - - pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self { - self.allowed_sides = allowed_sides; - self - } - - pub fn side(mut self, side: PanelSide) -> Self { - let allowed_sides = self.allowed_sides.allowed_sides(); - - if allowed_sides.contains(&side) { - self.current_side = side; - } else { - panic!( - "The panel side {:?} was not added as allowed before it was set.", - side - ); - } - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let token = token(); - let theme = theme(cx); - - let panel_base; - let current_width = self.width.unwrap_or(self.initial_width); - - match self.current_side { - PanelSide::Left => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_r() - .border_color(theme.middle.base.default.border); - } - PanelSide::Right => { - panel_base = v_stack() - .flex_initial() - .h_full() - .w(current_width) - .fill(theme.middle.base.default.background) - .border_l() - .border_color(theme.middle.base.default.border); - } - PanelSide::Bottom => { - panel_base = v_stack() - .flex_initial() - .w_full() - .h(current_width) - .fill(theme.middle.base.default.background) - .border_t() - .border_color(theme.middle.base.default.border); - } - } - - panel_base.children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/panes.rs b/crates/ui/src/components/panes.rs deleted file mode 100644 index 2518eea8ee..0000000000 --- a/crates/ui/src/components/panes.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::geometry::{Length, Size}; -use gpui2::{hsla, Hsla}; - -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum SplitDirection { - #[default] - Horizontal, - Vertical, -} - -#[derive(Element)] -pub struct Pane { - view_type: PhantomData, - scroll_state: ScrollState, - size: Size, - fill: Hsla, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Pane { - pub fn new( - scroll_state: ScrollState, - size: Size, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - // Fill is only here for debugging purposes, remove before release - let system_color = SystemColor::new(); - - Self { - view_type: PhantomData, - scroll_state, - size, - fill: hsla(0.3, 0.3, 0.3, 1.), - // fill: system_color.transparent, - children, - payload, - } - } - - pub fn fill(mut self, fill: Hsla) -> Self { - self.fill = fill; - self - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_initial() - .fill(self.fill) - .w(self.size.width) - .h(self.size.height) - .overflow_y_scroll(self.scroll_state.clone()) - .children_any((self.children)(cx, self.payload.as_ref())) - } -} - -#[derive(Element)] -pub struct PaneGroup { - view_type: PhantomData, - groups: Vec>, - panes: Vec>, - split_direction: SplitDirection, -} - -impl PaneGroup { - pub fn new_groups(groups: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups, - panes: Vec::new(), - split_direction, - } - } - - pub fn new_panes(panes: Vec>, split_direction: SplitDirection) -> Self { - Self { - view_type: PhantomData, - groups: Vec::new(), - panes, - split_direction, - } - } - - fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - if !self.panes.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.panes.iter_mut().map(|pane| pane.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - if !self.groups.is_empty() { - let el = div() - .flex() - .flex_1() - .gap_px() - .w_full() - .h_full() - .fill(theme.lowest.base.default.background) - .children(self.groups.iter_mut().map(|group| group.render(view, cx))); - - if self.split_direction == SplitDirection::Horizontal { - return el; - } else { - return el.flex_col(); - } - } - - unreachable!() - } -} diff --git a/crates/ui/src/components/player_stack.rs b/crates/ui/src/components/player_stack.rs deleted file mode 100644 index 7df6f065fb..0000000000 --- a/crates/ui/src/components/player_stack.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::prelude::*; -use crate::{Avatar, Facepile, PlayerWithCallStatus}; - -#[derive(Element)] -pub struct PlayerStack { - player_with_call_status: PlayerWithCallStatus, -} - -impl PlayerStack { - pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { - Self { - player_with_call_status, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let system_color = SystemColor::new(); - let player = self.player_with_call_status.get_player(); - self.player_with_call_status.get_call_status(); - - let followers = self - .player_with_call_status - .get_call_status() - .followers - .as_ref() - .map(|followers| followers.clone()); - - // if we have no followers return a slightly different element - // if mic_status == muted add a red ring to avatar - - div() - .h_full() - .flex() - .flex_col() - .gap_px() - .justify_center() - .child( - div().flex().justify_center().w_full().child( - div() - .w_4() - .h_0p5() - .rounded_sm() - .fill(player.cursor_color(cx)), - ), - ) - .child( - div() - .flex() - .items_center() - .justify_center() - .h_6() - .pl_1() - .rounded_lg() - .fill(if followers.is_none() { - system_color.transparent - } else { - player.selection_color(cx) - }) - .child(Avatar::new(player.avatar_src().to_string())) - .children(followers.map(|followers| { - div().neg_ml_2().child(Facepile::new(followers.into_iter())) - })), - ) - } -} diff --git a/crates/ui/src/components/project_panel.rs b/crates/ui/src/components/project_panel.rs deleted file mode 100644 index 1f32c698e5..0000000000 --- a/crates/ui/src/components/project_panel.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{ - static_project_panel_project_items, static_project_panel_single_items, theme, Input, List, - ListHeader, -}; - -#[derive(Element)] -pub struct ProjectPanel { - view_type: PhantomData, - scroll_state: ScrollState, -} - -impl ProjectPanel { - pub fn new(scroll_state: ScrollState) -> Self { - Self { - view_type: PhantomData, - scroll_state, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .flex() - .flex_col() - .w_full() - .h_full() - .px_2() - .fill(theme.middle.base.default.background) - .child( - div() - .w_56() - .flex() - .flex_col() - .overflow_y_scroll(ScrollState::default()) - .child( - List::new(static_project_panel_single_items()) - .header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled)) - .empty_message("No files in directory") - .set_toggle(ToggleState::Toggled), - ) - .child( - List::new(static_project_panel_project_items()) - .header(ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled)) - .empty_message("No folders in directory") - .set_toggle(ToggleState::Toggled), - ), - ) - .child( - Input::new("Find something...") - .value("buffe".to_string()) - .state(InteractionState::Focused), - ) - } -} diff --git a/crates/ui/src/components/recent_projects.rs b/crates/ui/src/components/recent_projects.rs deleted file mode 100644 index 6aca6631b9..0000000000 --- a/crates/ui/src/components/recent_projects.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct RecentProjects { - scroll_state: ScrollState, -} - -impl RecentProjects { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("zed").sublabel("~/projects/zed"), - PaletteItem::new("saga").sublabel("~/projects/saga"), - PaletteItem::new("journal").sublabel("~/journal"), - PaletteItem::new("dotfiles").sublabel("~/dotfiles"), - PaletteItem::new("zed.dev").sublabel("~/projects/zed.dev"), - PaletteItem::new("laminar").sublabel("~/projects/laminar"), - ]) - .placeholder("Recent Projects...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/status_bar.rs b/crates/ui/src/components/status_bar.rs deleted file mode 100644 index 1970b0bff9..0000000000 --- a/crates/ui/src/components/status_bar.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::theme::{theme, Theme}; -use crate::{Button, Icon, IconButton, IconColor, ToolDivider}; - -#[derive(Default, PartialEq)] -pub enum Tool { - #[default] - ProjectPanel, - CollaborationPanel, - Terminal, - Assistant, - Feedback, - Diagnostics, -} - -struct ToolGroup { - active_index: Option, - tools: Vec, -} - -impl Default for ToolGroup { - fn default() -> Self { - ToolGroup { - active_index: None, - tools: vec![], - } - } -} - -#[derive(Element)] -pub struct StatusBar { - view_type: PhantomData, - left_tools: Option, - right_tools: Option, - bottom_tools: Option, -} - -impl StatusBar { - pub fn new() -> Self { - Self { - view_type: PhantomData, - left_tools: None, - right_tools: None, - bottom_tools: None, - } - } - - pub fn left_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.left_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn right_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.right_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - pub fn bottom_tool(mut self, tool: Tool, active_index: Option) -> Self { - self.bottom_tools = { - let mut tools = vec![tool]; - tools.extend(self.left_tools.take().unwrap_or_default().tools); - Some(ToolGroup { - active_index, - tools, - }) - }; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .py_0p5() - .px_1() - .flex() - .items_center() - .justify_between() - .w_full() - .fill(theme.lowest.base.default.background) - .child(self.left_tools(&theme)) - .child(self.right_tools(&theme)) - } - - fn left_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FileTree).color(IconColor::Accent)) - .child(IconButton::new(Icon::Hash)) - .child(ToolDivider::new()) - .child(IconButton::new(Icon::XCircle)) - } - fn right_tools(&self, theme: &Theme) -> impl Element { - div() - .flex() - .items_center() - .gap_2() - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("116:25")) - .child(Button::new("Rust")), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Copilot)) - .child(IconButton::new(Icon::Envelope)), - ) - .child(ToolDivider::new()) - .child( - div() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Terminal)) - .child(IconButton::new(Icon::MessageBubbles)) - .child(IconButton::new(Icon::Ai)), - ) - } -} diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs deleted file mode 100644 index 9eb1122775..0000000000 --- a/crates/ui/src/components/tab.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::prelude::*; -use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor}; - -#[derive(Element, Clone)] -pub struct Tab { - title: String, - icon: Option, - current: bool, - dirty: bool, - fs_status: FileSystemStatus, - git_status: GitStatus, - diagnostic_status: DiagnosticStatus, - close_side: IconSide, -} - -impl Tab { - pub fn new() -> Self { - Self { - title: "untitled".to_string(), - icon: None, - current: false, - dirty: false, - fs_status: FileSystemStatus::None, - git_status: GitStatus::None, - diagnostic_status: DiagnosticStatus::None, - close_side: IconSide::Right, - } - } - - pub fn current(mut self, current: bool) -> Self { - self.current = current; - self - } - - pub fn title(mut self, title: String) -> Self { - self.title = title; - self - } - - pub fn icon(mut self, icon: I) -> Self - where - I: Into>, - { - self.icon = icon.into(); - self - } - - pub fn dirty(mut self, dirty: bool) -> Self { - self.dirty = dirty; - self - } - - pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self { - self.fs_status = fs_status; - self - } - - pub fn git_status(mut self, git_status: GitStatus) -> Self { - self.git_status = git_status; - self - } - - pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self { - self.diagnostic_status = diagnostic_status; - self - } - - pub fn close_side(mut self, close_side: IconSide) -> Self { - self.close_side = close_side; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; - let is_deleted = self.fs_status == FileSystemStatus::Deleted; - - let label = match (self.git_status, is_deleted) { - (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone()) - .color(LabelColor::Hidden) - .set_strikethrough(true), - (GitStatus::None, false) => Label::new(self.title.clone()), - (GitStatus::Created, false) => { - Label::new(self.title.clone()).color(LabelColor::Created) - } - (GitStatus::Modified, false) => { - Label::new(self.title.clone()).color(LabelColor::Modified) - } - (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent), - (GitStatus::Conflict, false) => Label::new(self.title.clone()), - }; - - let close_icon = IconElement::new(Icon::Close).color(IconColor::Muted); - - div() - .px_2() - .py_0p5() - .flex() - .items_center() - .justify_center() - .fill(if self.current { - theme.highest.base.default.background - } else { - theme.middle.base.default.background - }) - .child( - div() - .px_1() - .flex() - .items_center() - .gap_1() - .children(has_fs_conflict.then(|| { - IconElement::new(Icon::ExclamationTriangle) - .size(crate::IconSize::Small) - .color(IconColor::Warning) - })) - .children(self.icon.map(IconElement::new)) - .children(if self.close_side == IconSide::Left { - Some(close_icon.clone()) - } else { - None - }) - .child(label) - .children(if self.close_side == IconSide::Right { - Some(close_icon) - } else { - None - }), - ) - } -} diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs deleted file mode 100644 index 8addcb87b1..0000000000 --- a/crates/ui/src/components/tab_bar.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::marker::PhantomData; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Tab}; - -#[derive(Element)] -pub struct TabBar { - view_type: PhantomData, - scroll_state: ScrollState, - tabs: Vec, -} - -impl TabBar { - pub fn new(tabs: Vec) -> Self { - Self { - view_type: PhantomData, - scroll_state: ScrollState::default(), - tabs, - } - } - - pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) { - self.scroll_state = scroll_state; - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - // Left Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft) - .state(InteractionState::Enabled.if_enabled(can_navigate_back)), - ) - .child( - IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - ), - ), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .overflow_x_scroll(self.scroll_state.clone()) - .children(self.tabs.clone()), - ), - ) - // Right Side - .child( - div() - .px_1() - .flex() - .flex_none() - .gap_2() - // Nav Buttons - .child( - div() - .flex() - .items_center() - .gap_px() - .child(IconButton::new(Icon::Plus)) - .child(IconButton::new(Icon::Split)), - ), - ) - } -} diff --git a/crates/ui/src/components/terminal.rs b/crates/ui/src/components/terminal.rs deleted file mode 100644 index 909cb886ce..0000000000 --- a/crates/ui/src/components/terminal.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use gpui2::geometry::{relative, rems, Size}; - -use crate::prelude::*; -use crate::{theme, Icon, IconButton, Pane, Tab}; - -#[derive(Element)] -pub struct Terminal {} - -impl Terminal { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let can_navigate_back = true; - let can_navigate_forward = false; - - div() - .flex() - .flex_col() - .w_full() - .child( - // Terminal Tabs. - div() - .w_full() - .flex() - .fill(theme.middle.base.default.background) - .child( - div().px_1().flex().flex_none().gap_2().child( - div() - .flex() - .items_center() - .gap_px() - .child( - IconButton::new(Icon::ArrowLeft).state( - InteractionState::Enabled.if_enabled(can_navigate_back), - ), - ) - .child(IconButton::new(Icon::ArrowRight).state( - InteractionState::Enabled.if_enabled(can_navigate_forward), - )), - ), - ) - .child( - div().w_0().flex_1().h_full().child( - div() - .flex() - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(true), - ) - .child( - Tab::new() - .title("zed — fish".to_string()) - .icon(Icon::Terminal) - .close_side(IconSide::Right) - .current(false), - ), - ), - ), - ) - // Terminal Pane. - .child(Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: rems(36.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![crate::static_data::terminal_buffer(&theme).into_any()] - }, - Box::new(theme), - )) - } -} diff --git a/crates/ui/src/components/theme_selector.rs b/crates/ui/src/components/theme_selector.rs deleted file mode 100644 index e6f5237afe..0000000000 --- a/crates/ui/src/components/theme_selector.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::prelude::*; -use crate::{OrderMethod, Palette, PaletteItem}; - -#[derive(Element)] -pub struct ThemeSelector { - scroll_state: ScrollState, -} - -impl ThemeSelector { - pub fn new() -> Self { - Self { - scroll_state: ScrollState::default(), - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - div().child( - Palette::new(self.scroll_state.clone()) - .items(vec![ - PaletteItem::new("One Dark"), - PaletteItem::new("Rosé Pine"), - PaletteItem::new("Rosé Pine Moon"), - PaletteItem::new("Sandcastle"), - PaletteItem::new("Solarized Dark"), - PaletteItem::new("Summercamp"), - PaletteItem::new("Atelier Cave Light"), - PaletteItem::new("Atelier Dune Light"), - PaletteItem::new("Atelier Estuary Light"), - PaletteItem::new("Atelier Forest Light"), - PaletteItem::new("Atelier Heath Light"), - ]) - .placeholder("Select Theme...") - .empty_string("No matches") - .default_order(OrderMethod::Ascending), - ) - } -} diff --git a/crates/ui/src/components/title_bar.rs b/crates/ui/src/components/title_bar.rs deleted file mode 100644 index dd3d6c1fba..0000000000 --- a/crates/ui/src/components/title_bar.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::marker::PhantomData; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use crate::{prelude::*, PlayerWithCallStatus}; -use crate::{ - theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, ToolDivider, TrafficLights, -}; - -#[derive(Clone)] -pub struct Livestream { - pub players: Vec, - pub channel: Option, // projects - // windows -} - -#[derive(Element)] -pub struct TitleBar { - view_type: PhantomData, - /// If the window is active from the OS's perspective. - is_active: Arc, - livestream: Option, -} - -impl TitleBar { - pub fn new(cx: &mut ViewContext) -> Self { - let is_active = Arc::new(AtomicBool::new(true)); - let active = is_active.clone(); - - cx.observe_window_activation(move |_, is_active, cx| { - active.store(is_active, std::sync::atomic::Ordering::SeqCst); - cx.notify(); - }) - .detach(); - - Self { - view_type: PhantomData, - is_active, - livestream: None, - } - } - - pub fn set_livestream(mut self, livestream: Option) -> Self { - self.livestream = livestream; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let has_focus = cx.window_is_active(); - - let player_list = if let Some(livestream) = &self.livestream { - livestream.players.clone().into_iter() - } else { - vec![].into_iter() - }; - - div() - .flex() - .items_center() - .justify_between() - .w_full() - .h_8() - .fill(theme.lowest.base.default.background) - .child( - div() - .flex() - .items_center() - .h_full() - .gap_4() - .px_2() - .child(TrafficLights::new().window_has_focus(has_focus)) - // === Project Info === // - .child( - div() - .flex() - .items_center() - .gap_1() - .child(Button::new("zed")) - .child(Button::new("nate/gpui2-ui-components")), - ) - .children(player_list.map(|p| PlayerStack::new(p))) - .child(IconButton::new(Icon::Plus)), - ) - .child( - div() - .flex() - .items_center() - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::FolderX)) - .child(IconButton::new(Icon::Close)), - ) - .child(ToolDivider::new()) - .child( - div() - .px_2() - .flex() - .items_center() - .gap_1() - .child(IconButton::new(Icon::Mic)) - .child(IconButton::new(Icon::AudioOn)) - .child(IconButton::new(Icon::Screen).color(IconColor::Accent)), - ) - .child( - div().px_2().flex().items_center().child( - Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4") - .shape(Shape::RoundedRectangle), - ), - ), - ) - } -} diff --git a/crates/ui/src/components/toast.rs b/crates/ui/src/components/toast.rs deleted file mode 100644 index c299cdd6bc..0000000000 --- a/crates/ui/src/components/toast.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::prelude::*; - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastOrigin { - #[default] - Bottom, - BottomRight, -} - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum ToastVariant { - #[default] - Toast, - Status, -} - -/// A toast is a small, temporary window that appears to show a message to the user -/// or indicate a required action. -/// -/// Toasts should not persist on the screen for more than a few seconds unless -/// they are actively showing the a process in progress. -/// -/// Only one toast may be visible at a time. -#[derive(Element)] -pub struct Toast { - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, -} - -impl Toast { - pub fn new( - origin: ToastOrigin, - children: HackyChildren, - payload: HackyChildrenPayload, - ) -> Self { - Self { - origin, - children, - payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let color = ThemeColor::new(cx); - - let mut div = div(); - - if self.origin == ToastOrigin::Bottom { - div = div.right_1_2(); - } else { - div = div.right_4(); - } - - div.absolute() - .bottom_4() - .flex() - .py_2() - .px_1p5() - .min_w_40() - .rounded_md() - .fill(color.elevated_surface) - .max_w_64() - .children_any((self.children)(cx, self.payload.as_ref())) - } -} diff --git a/crates/ui/src/components/toolbar.rs b/crates/ui/src/components/toolbar.rs deleted file mode 100644 index e0953bf3b2..0000000000 --- a/crates/ui/src/components/toolbar.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Clone)] -pub struct ToolbarItem {} - -#[derive(Element)] -pub struct Toolbar { - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, -} - -impl Toolbar { - pub fn new( - left_items: HackyChildren, - left_items_payload: HackyChildrenPayload, - right_items: HackyChildren, - right_items_payload: HackyChildrenPayload, - ) -> Self { - Self { - left_items, - left_items_payload, - right_items, - right_items_payload, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - .fill(theme.highest.base.default.background) - .p_2() - .flex() - .justify_between() - .child( - div() - .flex() - .children_any((self.left_items)(cx, self.left_items_payload.as_ref())), - ) - .child( - div() - .flex() - .children_any((self.right_items)(cx, self.right_items_payload.as_ref())), - ) - } -} diff --git a/crates/ui/src/components/traffic_lights.rs b/crates/ui/src/components/traffic_lights.rs deleted file mode 100644 index 0d644c49ca..0000000000 --- a/crates/ui/src/components/traffic_lights.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::prelude::*; -use crate::{theme, token, SystemColor}; - -#[derive(Clone, Copy)] -enum TrafficLightColor { - Red, - Yellow, - Green, -} - -#[derive(Element)] -struct TrafficLight { - color: TrafficLightColor, - window_has_focus: bool, -} - -impl TrafficLight { - fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { - Self { - color, - window_has_focus, - } - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green, - (false, _) => theme.lowest.base.active.background, - }; - - div().w_3().h_3().rounded_full().fill(fill) - } -} - -#[derive(Element)] -pub struct TrafficLights { - window_has_focus: bool, -} - -impl TrafficLights { - pub fn new() -> Self { - Self { - window_has_focus: true, - } - } - - pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { - self.window_has_focus = window_has_focus; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let token = token(); - - div() - .flex() - .items_center() - .gap_2() - .child(TrafficLight::new( - TrafficLightColor::Red, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Yellow, - self.window_has_focus, - )) - .child(TrafficLight::new( - TrafficLightColor::Green, - self.window_has_focus, - )) - } -} diff --git a/crates/ui/src/components/workspace.rs b/crates/ui/src/components/workspace.rs deleted file mode 100644 index b3d375bd64..0000000000 --- a/crates/ui/src/components/workspace.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::sync::Arc; - -use chrono::DateTime; -use gpui2::geometry::{relative, rems, Size}; - -use crate::{ - hello_world_rust_editor_with_status_example, prelude::*, random_players_with_call_status, - Livestream, -}; -use crate::{ - theme, v_stack, ChatMessage, ChatPanel, EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides, - PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, -}; - -#[derive(Element, Default)] -pub struct WorkspaceElement { - left_panel_scroll_state: ScrollState, - right_panel_scroll_state: ScrollState, - tab_bar_scroll_state: ScrollState, - bottom_panel_scroll_state: ScrollState, -} - -impl WorkspaceElement { - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx).clone(); - - let temp_size = rems(36.).into(); - - let root_group = PaneGroup::new_groups( - vec![ - PaneGroup::new_panes( - vec![ - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - ), - Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: temp_size, - }, - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ), - ], - SplitDirection::Vertical, - ), - PaneGroup::new_panes( - vec![Pane::new( - ScrollState::default(), - Size { - width: relative(1.).into(), - height: relative(1.).into(), - }, - |_, payload| { - let theme = payload.downcast_ref::>().unwrap(); - - vec![EditorPane::new(hello_world_rust_editor_with_status_example( - &theme, - )) - .into_any()] - }, - Box::new(theme.clone()), - )], - SplitDirection::Vertical, - ), - ], - SplitDirection::Horizontal, - ); - - div() - .relative() - .size_full() - .flex() - .flex_col() - .font("Zed Sans Extended") - .gap_0() - .justify_start() - .items_start() - .text_color(theme.lowest.base.default.foreground) - .fill(theme.lowest.base.default.background) - .child(TitleBar::new(cx).set_livestream(Some(Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - }))) - .child( - div() - .flex_1() - .w_full() - .flex() - .flex_row() - .overflow_hidden() - .border_t() - .border_b() - .border_color(theme.lowest.base.default.border) - .child( - Panel::new( - self.left_panel_scroll_state.clone(), - |_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()], - Box::new(()), - ) - .side(PanelSide::Left), - ) - .child( - v_stack() - .flex_1() - .h_full() - .child( - div() - .flex() - .flex_1() - // CSS Hack: Flex 1 has to have a set height to properly fill the space - // Or it will give you a height of 0 - .h_px() - .child(root_group), - ) - .child( - Panel::new( - self.bottom_panel_scroll_state.clone(), - |_, _| vec![Terminal::new().into_any()], - Box::new(()), - ) - .allowed_sides(PanelAllowedSides::BottomOnly) - .side(PanelSide::Bottom), - ), - ) - .child( - Panel::new( - self.right_panel_scroll_state.clone(), - |_, payload| { - vec![ChatPanel::new(ScrollState::default()) - .with_messages(vec![ - ChatMessage::new( - "osiewicz".to_string(), - "is this thing on?".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-27T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ChatMessage::new( - "maxdeviant".to_string(), - "Reading you loud and clear!".to_string(), - DateTime::parse_from_rfc3339( - "2023-09-28T15:40:52.707Z", - ) - .unwrap() - .naive_local(), - ), - ]) - .into_any()] - }, - Box::new(()), - ) - .side(PanelSide::Right), - ), - ) - .child(StatusBar::new()) - // An example of a toast is below - // Currently because of stacking order this gets obscured by other elements - - // .child(Toast::new( - // ToastOrigin::Bottom, - // |_, payload| { - // let theme = payload.downcast_ref::>().unwrap(); - - // vec![Label::new("label").into_any()] - // }, - // Box::new(theme.clone()), - // )) - } -} diff --git a/crates/ui/src/element_ext.rs b/crates/ui/src/element_ext.rs deleted file mode 100644 index 67352f0779..0000000000 --- a/crates/ui/src/element_ext.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::marker::PhantomData; - -use gpui2::Element; - -use crate::theme::{Theme, Themed}; - -pub trait ElementExt: Element { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized; -} - -impl> ElementExt for E { - fn themed(self, theme: Theme) -> Themed - where - Self: Sized, - { - Themed { - child: self, - theme, - view_type: PhantomData, - } - } -} diff --git a/crates/ui/src/elements.rs b/crates/ui/src/elements.rs deleted file mode 100644 index c60902ae98..0000000000 --- a/crates/ui/src/elements.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod avatar; -mod button; -mod details; -mod icon; -mod input; -mod label; -mod player; -mod stack; -mod tool_divider; - -pub use avatar::*; -pub use button::*; -pub use details::*; -pub use icon::*; -pub use input::*; -pub use label::*; -pub use player::*; -pub use stack::*; -pub use tool_divider::*; diff --git a/crates/ui/src/elements/avatar.rs b/crates/ui/src/elements/avatar.rs deleted file mode 100644 index 2072b0e501..0000000000 --- a/crates/ui/src/elements/avatar.rs +++ /dev/null @@ -1,41 +0,0 @@ -use gpui2::elements::img; -use gpui2::ArcCow; - -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Avatar { - src: ArcCow<'static, str>, - shape: Shape, -} - -impl Avatar { - pub fn new(src: impl Into>) -> Self { - Self { - src: src.into(), - shape: Shape::Circle, - } - } - - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let mut img = img(); - - if self.shape == Shape::Circle { - img = img.rounded_full(); - } else { - img = img.rounded_md(); - } - - img.uri(self.src.clone()) - .size_4() - .fill(theme.middle.warning.default.foreground) - } -} diff --git a/crates/ui/src/elements/button.rs b/crates/ui/src/elements/button.rs deleted file mode 100644 index c516b27908..0000000000 --- a/crates/ui/src/elements/button.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::rc::Rc; - -use gpui2::geometry::DefiniteLength; -use gpui2::platform::MouseButton; -use gpui2::{EventContext, Hsla, Interactive, WindowContext}; - -use crate::prelude::*; -use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize}; - -#[derive(Default, PartialEq, Clone, Copy)] -pub enum IconPosition { - #[default] - Left, - Right, -} - -#[derive(Default, Copy, Clone, PartialEq)] -pub enum ButtonVariant { - #[default] - Ghost, - Filled, -} - -struct ButtonHandlers { - click: Option)>>, -} - -impl Default for ButtonHandlers { - fn default() -> Self { - Self { click: None } - } -} - -#[derive(Element)] -pub struct Button { - label: String, - variant: ButtonVariant, - state: InteractionState, - icon: Option, - icon_position: Option, - width: Option, - handlers: ButtonHandlers, -} - -impl Button { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - variant: Default::default(), - state: Default::default(), - icon: None, - icon_position: None, - width: Default::default(), - handlers: ButtonHandlers::default(), - } - } - - pub fn ghost(label: L) -> Self - where - L: Into, - { - Self::new(label).variant(ButtonVariant::Ghost) - } - - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn icon(mut self, icon: Icon) -> Self { - self.icon = Some(icon); - self - } - - pub fn icon_position(mut self, icon_position: IconPosition) -> Self { - if self.icon.is_none() { - panic!("An icon must be present if an icon_position is provided."); - } - self.icon_position = Some(icon_position); - self - } - - pub fn width(mut self, width: Option) -> Self { - self.width = width; - self - } - - pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext) + 'static) -> Self { - self.handlers.click = Some(Rc::new(handler)); - self - } - - fn background_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match (self.variant, self.state) { - (ButtonVariant::Ghost, InteractionState::Hovered) => { - theme.lowest.base.hovered.background - } - (ButtonVariant::Ghost, InteractionState::Active) => { - theme.lowest.base.pressed.background - } - (ButtonVariant::Filled, InteractionState::Enabled) => { - theme.lowest.on.default.background - } - (ButtonVariant::Filled, InteractionState::Hovered) => { - theme.lowest.on.hovered.background - } - (ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background, - (ButtonVariant::Filled, InteractionState::Disabled) => { - theme.lowest.on.disabled.background - } - _ => system_color.transparent, - } - } - - fn label_color(&self) -> LabelColor { - match self.state { - InteractionState::Disabled => LabelColor::Disabled, - _ => Default::default(), - } - } - - fn icon_color(&self) -> IconColor { - match self.state { - InteractionState::Disabled => IconColor::Disabled, - _ => Default::default(), - } - } - - fn border_color(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self.state { - InteractionState::Focused => theme.lowest.accent.default.border, - _ => system_color.transparent, - } - } - - fn render_label(&self) -> Label { - Label::new(self.label.clone()) - .size(LabelSize::Small) - .color(self.label_color()) - } - - fn render_icon(&self, icon_color: IconColor) -> Option { - self.icon.map(|i| IconElement::new(i).color(icon_color)) - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let icon_color = self.icon_color(); - let system_color = SystemColor::new(); - let border_color = self.border_color(cx); - - let mut el = h_stack() - .h_6() - .px_1() - .items_center() - .rounded_md() - .border() - .border_color(border_color) - .fill(self.background_color(cx)); - - match (self.icon, self.icon_position) { - (Some(_), Some(IconPosition::Left)) => { - el = el - .gap_1() - .child(self.render_label()) - .children(self.render_icon(icon_color)) - } - (Some(_), Some(IconPosition::Right)) => { - el = el - .gap_1() - .children(self.render_icon(icon_color)) - .child(self.render_label()) - } - (_, _) => el = el.child(self.render_label()), - } - - if let Some(width) = self.width { - el = el.w(width).justify_center(); - } - - if let Some(click_handler) = self.handlers.click.clone() { - el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| { - click_handler(view, cx); - }); - } - - el - } -} diff --git a/crates/ui/src/elements/details.rs b/crates/ui/src/elements/details.rs deleted file mode 100644 index 9c829bcd41..0000000000 --- a/crates/ui/src/elements/details.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element, Clone)] -pub struct Details { - text: &'static str, - meta: Option<&'static str>, -} - -impl Details { - pub fn new(text: &'static str) -> Self { - Self { text, meta: None } - } - - pub fn meta_text(mut self, meta: &'static str) -> Self { - self.meta = Some(meta); - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div() - // .flex() - // .w_full() - .p_1() - .gap_0p5() - .text_xs() - .text_color(theme.lowest.base.default.foreground) - .child(self.text) - .children(self.meta.map(|m| m)) - } -} diff --git a/crates/ui/src/elements/icon.rs b/crates/ui/src/elements/icon.rs deleted file mode 100644 index 26bf7dab22..0000000000 --- a/crates/ui/src/elements/icon.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::sync::Arc; - -use gpui2::elements::svg; -use gpui2::Hsla; -use strum::EnumIter; - -use crate::prelude::*; -use crate::theme::theme; -use crate::Theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconSize { - Small, - #[default] - Large, -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum IconColor { - #[default] - Default, - Muted, - Disabled, - Placeholder, - Accent, - Error, - Warning, - Success, - Info, -} - -impl IconColor { - pub fn color(self, theme: Arc) -> Hsla { - match self { - IconColor::Default => theme.lowest.base.default.foreground, - IconColor::Muted => theme.lowest.variant.default.foreground, - IconColor::Disabled => theme.lowest.base.disabled.foreground, - IconColor::Placeholder => theme.lowest.base.disabled.foreground, - IconColor::Accent => theme.lowest.accent.default.foreground, - IconColor::Error => theme.lowest.negative.default.foreground, - IconColor::Warning => theme.lowest.warning.default.foreground, - IconColor::Success => theme.lowest.positive.default.foreground, - IconColor::Info => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { - Ai, - ArrowLeft, - ArrowRight, - ArrowUpRight, - AudioOff, - AudioOn, - Bolt, - ChevronDown, - ChevronLeft, - ChevronRight, - ChevronUp, - Close, - ExclamationTriangle, - ExternalLink, - File, - FileGeneric, - FileDoc, - FileGit, - FileLock, - FileRust, - FileToml, - FileTree, - Folder, - FolderOpen, - FolderX, - #[default] - Hash, - InlayHint, - MagicWand, - MagnifyingGlass, - Maximize, - Menu, - MessageBubbles, - Mic, - MicMute, - Plus, - Quote, - Screen, - SelectAll, - Split, - SplitMessage, - Terminal, - XCircle, - Copilot, - Envelope, -} - -impl Icon { - pub fn path(self) -> &'static str { - match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::AudioOff => "icons/speaker-off.svg", - Icon::AudioOn => "icons/speaker-loud.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::MagicWand => "icons/magic-wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic-mute.svg", - Icon::Plus => "icons/plus.svg", - Icon::Quote => "icons/quote.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select-all.svg", - Icon::Split => "icons/split.svg", - Icon::SplitMessage => "icons/split_message.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::XCircle => "icons/error.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::Envelope => "icons/feedback.svg", - } - } -} - -#[derive(Element, Clone)] -pub struct IconElement { - icon: Icon, - color: IconColor, - size: IconSize, -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { - Self { - icon, - color: IconColor::default(), - size: IconSize::default(), - } - } - - pub fn color(mut self, color: IconColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: IconSize) -> Self { - self.size = size; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - let fill = self.color.color(theme); - - let sized_svg = match self.size { - IconSize::Small => svg().size_3p5(), - IconSize::Large => svg().size_4(), - }; - - sized_svg.flex_none().path(self.icon.path()).fill(fill) - } -} diff --git a/crates/ui/src/elements/input.rs b/crates/ui/src/elements/input.rs deleted file mode 100644 index fd860f30c2..0000000000 --- a/crates/ui/src/elements/input.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Default, PartialEq)] -pub enum InputVariant { - #[default] - Ghost, - Filled, -} - -#[derive(Element)] -pub struct Input { - placeholder: &'static str, - value: String, - state: InteractionState, - variant: InputVariant, -} - -impl Input { - pub fn new(placeholder: &'static str) -> Self { - Self { - placeholder, - value: "".to_string(), - state: InteractionState::default(), - variant: InputVariant::default(), - } - } - - pub fn value(mut self, value: String) -> Self { - self.value = value; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn variant(mut self, variant: InputVariant) -> Self { - self.variant = variant; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let text_el; - let text_color; - let background_color_default; - let background_color_active; - - let mut border_color_default = theme.middle.base.default.border; - let mut border_color_hover = theme.middle.base.hovered.border; - let mut border_color_active = theme.middle.base.pressed.border; - let border_color_focus = theme.middle.base.pressed.background; - - match self.variant { - InputVariant::Ghost => { - background_color_default = theme.middle.base.default.background; - background_color_active = theme.middle.base.active.background; - } - InputVariant::Filled => { - background_color_default = theme.middle.on.default.background; - background_color_active = theme.middle.on.active.background; - } - }; - - if self.state == InteractionState::Focused { - border_color_default = theme.players[0].cursor; - border_color_hover = theme.players[0].cursor; - border_color_active = theme.players[0].cursor; - } - - if self.state == InteractionState::Focused || self.state == InteractionState::Active { - text_el = self.value.clone(); - text_color = theme.lowest.base.default.foreground; - } else { - text_el = self.placeholder.to_string().clone(); - text_color = theme.lowest.base.disabled.foreground; - } - - div() - .h_7() - .w_full() - .px_2() - .border() - .border_color(border_color_default) - .fill(background_color_default) - .hover() - .border_color(border_color_hover) - .active() - .border_color(border_color_active) - .fill(background_color_active) - .flex() - .items_center() - .child( - div() - .flex() - .items_center() - .text_sm() - .text_color(text_color) - .child(text_el) - .child(div().text_color(theme.players[0].cursor).child("|")), - ) - } -} diff --git a/crates/ui/src/elements/label.rs b/crates/ui/src/elements/label.rs deleted file mode 100644 index e7b15aaf02..0000000000 --- a/crates/ui/src/elements/label.rs +++ /dev/null @@ -1,161 +0,0 @@ -use gpui2::{Hsla, WindowContext}; -use smallvec::SmallVec; - -use crate::prelude::*; -use crate::theme::theme; - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelColor { - #[default] - Default, - Muted, - Created, - Modified, - Deleted, - Disabled, - Hidden, - Placeholder, - Accent, -} - -impl LabelColor { - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - - match self { - Self::Default => theme.middle.base.default.foreground, - Self::Muted => theme.middle.variant.default.foreground, - Self::Created => theme.middle.positive.default.foreground, - Self::Modified => theme.middle.warning.default.foreground, - Self::Deleted => theme.middle.negative.default.foreground, - Self::Disabled => theme.middle.base.disabled.foreground, - Self::Hidden => theme.middle.variant.default.foreground, - Self::Placeholder => theme.middle.base.disabled.foreground, - Self::Accent => theme.middle.accent.default.foreground, - } - } -} - -#[derive(Default, PartialEq, Copy, Clone)] -pub enum LabelSize { - #[default] - Default, - Small, -} - -#[derive(Element, Clone)] -pub struct Label { - label: String, - color: LabelColor, - size: LabelSize, - highlight_indices: Vec, - strikethrough: bool, -} - -impl Label { - pub fn new(label: L) -> Self - where - L: Into, - { - Self { - label: label.into(), - color: LabelColor::Default, - size: LabelSize::Default, - highlight_indices: Vec::new(), - strikethrough: false, - } - } - - pub fn color(mut self, color: LabelColor) -> Self { - self.color = color; - self - } - - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn with_highlights(mut self, indices: Vec) -> Self { - self.highlight_indices = indices; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - let highlight_color = theme.lowest.accent.default.foreground; - - let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); - - let mut runs: SmallVec<[Run; 8]> = SmallVec::new(); - - for (char_ix, char) in self.label.char_indices() { - let mut color = self.color.hsla(cx); - - if let Some(highlight_ix) = highlight_indices.peek() { - if char_ix == *highlight_ix { - color = highlight_color; - - highlight_indices.next(); - } - } - - let last_run = runs.last_mut(); - - let start_new_run = if let Some(last_run) = last_run { - if color == last_run.color { - last_run.text.push(char); - false - } else { - true - } - } else { - true - }; - - if start_new_run { - runs.push(Run { - text: char.to_string(), - color, - }); - } - } - - div() - .flex() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_px() - .my_auto() - .w_full() - .h_px() - .fill(LabelColor::Hidden.hsla(cx)), - ) - }) - .children(runs.into_iter().map(|run| { - let mut div = div(); - - if self.size == LabelSize::Small { - div = div.text_xs(); - } else { - div = div.text_sm(); - } - - div.text_color(run.color).child(run.text) - })) - } -} - -/// A run of text that receives the same style. -struct Run { - pub text: String, - pub color: Hsla, -} diff --git a/crates/ui/src/elements/player.rs b/crates/ui/src/elements/player.rs deleted file mode 100644 index 465542dc7f..0000000000 --- a/crates/ui/src/elements/player.rs +++ /dev/null @@ -1,133 +0,0 @@ -use gpui2::{Hsla, ViewContext}; - -use crate::theme; - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum PlayerStatus { - #[default] - Offline, - Online, - InCall, - Away, - DoNotDisturb, - Invisible, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum MicStatus { - Muted, - #[default] - Unmuted, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum VideoStatus { - On, - #[default] - Off, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum ScreenShareStatus { - Shared, - #[default] - NotShared, -} - -#[derive(Clone)] -pub struct PlayerCallStatus { - pub mic_status: MicStatus, - /// Indicates if the player is currently speaking - /// And the intensity of the volume coming through - /// - /// 0.0 - 1.0 - pub voice_activity: f32, - pub video_status: VideoStatus, - pub screen_share_status: ScreenShareStatus, - pub in_current_project: bool, - pub disconnected: bool, - pub following: Option>, - pub followers: Option>, -} - -impl PlayerCallStatus { - pub fn new() -> Self { - Self { - mic_status: MicStatus::default(), - voice_activity: 0., - video_status: VideoStatus::default(), - screen_share_status: ScreenShareStatus::default(), - in_current_project: true, - disconnected: false, - following: None, - followers: None, - } - } -} - -#[derive(PartialEq, Clone)] -pub struct Player { - index: usize, - avatar_src: String, - username: String, - status: PlayerStatus, -} - -#[derive(Clone)] -pub struct PlayerWithCallStatus { - player: Player, - call_status: PlayerCallStatus, -} - -impl PlayerWithCallStatus { - pub fn new(player: Player, call_status: PlayerCallStatus) -> Self { - Self { - player, - call_status, - } - } - - pub fn get_player(&self) -> &Player { - &self.player - } - - pub fn get_call_status(&self) -> &PlayerCallStatus { - &self.call_status - } -} - -impl Player { - pub fn new(index: usize, avatar_src: String, username: String) -> Self { - Self { - index, - avatar_src, - username, - status: Default::default(), - } - } - - pub fn set_status(mut self, status: PlayerStatus) -> Self { - self.status = status; - self - } - - pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].cursor - } - - pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - let index = self.index % 8; - theme.players[self.index].selection - } - - pub fn avatar_src(&self) -> &str { - &self.avatar_src - } - - pub fn index(&self) -> usize { - self.index - } -} diff --git a/crates/ui/src/elements/stack.rs b/crates/ui/src/elements/stack.rs deleted file mode 100644 index ef186f5ebe..0000000000 --- a/crates/ui/src/elements/stack.rs +++ /dev/null @@ -1,31 +0,0 @@ -use gpui2::elements::div::Div; - -use crate::prelude::*; - -pub trait Stack: StyleHelpers { - /// Horizontally stacks elements. - fn h_stack(self) -> Self { - self.flex().flex_row().items_center() - } - - /// Vertically stacks elements. - fn v_stack(self) -> Self { - self.flex().flex_col() - } -} - -impl Stack for Div {} - -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` -pub fn h_stack() -> Div { - div().h_stack() -} - -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` -pub fn v_stack() -> Div { - div().v_stack() -} diff --git a/crates/ui/src/elements/tool_divider.rs b/crates/ui/src/elements/tool_divider.rs deleted file mode 100644 index 8b5a191445..0000000000 --- a/crates/ui/src/elements/tool_divider.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::prelude::*; -use crate::theme; - -#[derive(Element)] -pub struct ToolDivider {} - -impl ToolDivider { - pub fn new() -> Self { - Self {} - } - - fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { - let theme = theme(cx); - - div().w_px().h_3().fill(theme.lowest.base.default.border) - } -} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index 39156d3ab4..0000000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(dead_code, unused_variables)] - -mod children; -mod components; -mod element_ext; -mod elements; -pub mod prelude; -mod static_data; -mod theme; -mod tokens; - -pub use children::*; -pub use components::*; -pub use element_ext::*; -pub use elements::*; -pub use prelude::*; -pub use static_data::*; -pub use tokens::*; - -pub use crate::theme::*; diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs deleted file mode 100644 index e0da3579e2..0000000000 --- a/crates/ui/src/prelude.rs +++ /dev/null @@ -1,274 +0,0 @@ -pub use gpui2::elements::div::{div, ScrollState}; -pub use gpui2::style::{StyleHelpers, Styleable}; -pub use gpui2::{Element, IntoElement, ParentElement, ViewContext}; - -pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant, Theme}; - -use gpui2::{hsla, rgb, Hsla, WindowContext}; -use strum::EnumIter; - -#[derive(Default)] -pub struct SystemColor { - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, -} - -impl SystemColor { - pub fn new() -> SystemColor { - SystemColor { - transparent: hsla(0.0, 0.0, 0.0, 0.0), - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - } - } - pub fn color(&self) -> Hsla { - self.transparent - } -} - -#[derive(Clone, Copy)] -pub struct ThemeColor { - pub border: Hsla, - pub border_variant: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, -} - -impl ThemeColor { - pub fn new(cx: &WindowContext) -> Self { - let theme = theme(cx); - - Self { - border: theme.lowest.base.default.border, - border_variant: theme.lowest.variant.default.border, - elevated_surface: theme.middle.base.default.background, - } - } -} - -#[derive(Default, PartialEq, EnumIter, Clone, Copy)] -pub enum HighlightColor { - #[default] - Default, - Comment, - String, - Function, - Keyword, -} - -impl HighlightColor { - pub fn hsla(&self, theme: &Theme) -> Hsla { - let system_color = SystemColor::new(); - - match self { - Self::Default => theme - .syntax - .get("primary") - .expect("no theme.syntax.primary") - .clone(), - Self::Comment => theme - .syntax - .get("comment") - .expect("no theme.syntax.comment") - .clone(), - Self::String => theme - .syntax - .get("string") - .expect("no theme.syntax.string") - .clone(), - Self::Function => theme - .syntax - .get("function") - .expect("no theme.syntax.function") - .clone(), - Self::Keyword => theme - .syntax - .get("keyword") - .expect("no theme.syntax.keyword") - .clone(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum FileSystemStatus { - #[default] - None, - Conflict, - Deleted, -} - -impl FileSystemStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Deleted => "Deleted".to_string(), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum GitStatus { - #[default] - None, - Created, - Modified, - Deleted, - Conflict, - Renamed, -} - -impl GitStatus { - pub fn to_string(&self) -> String { - match self { - Self::None => "None".to_string(), - Self::Created => "Created".to_string(), - Self::Modified => "Modified".to_string(), - Self::Deleted => "Deleted".to_string(), - Self::Conflict => "Conflict".to_string(), - Self::Renamed => "Renamed".to_string(), - } - } - - pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - let system_color = SystemColor::new(); - - match self { - Self::None => system_color.transparent, - Self::Created => theme.lowest.positive.default.foreground, - Self::Modified => theme.lowest.warning.default.foreground, - Self::Deleted => theme.lowest.negative.default.foreground, - Self::Conflict => theme.lowest.warning.default.foreground, - Self::Renamed => theme.lowest.accent.default.foreground, - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DiagnosticStatus { - #[default] - None, - Error, - Warning, - Info, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum IconSide { - #[default] - Left, - Right, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum OrderMethod { - #[default] - Ascending, - Descending, - MostRecent, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum Shape { - #[default] - Circle, - RoundedRectangle, -} - -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] -pub enum DisclosureControlVisibility { - #[default] - OnHover, - Always, -} - -#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)] -pub enum InteractionState { - #[default] - Enabled, - Hovered, - Active, - Focused, - Disabled, -} - -impl InteractionState { - pub fn if_enabled(&self, enabled: bool) -> Self { - if enabled { - *self - } else { - InteractionState::Disabled - } - } -} - -#[derive(Default, PartialEq)] -pub enum SelectedState { - #[default] - Unselected, - PartiallySelected, - Selected, -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum Toggleable { - Toggleable(ToggleState), - #[default] - NotToggleable, -} - -impl Toggleable { - pub fn is_toggled(&self) -> bool { - match self { - Self::Toggleable(ToggleState::Toggled) => true, - _ => false, - } - } -} - -impl From for Toggleable { - fn from(state: ToggleState) -> Self { - Self::Toggleable(state) - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -pub enum ToggleState { - /// The "on" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently expanded - /// - A toggle button that is currently on. - Toggled, - /// The "off" state of a toggleable element. - /// - /// Example: - /// - A collasable list that is currently collapsed - /// - A toggle button that is currently off. - #[default] - NotToggled, -} - -impl From for ToggleState { - fn from(toggleable: Toggleable) -> Self { - match toggleable { - Toggleable::Toggleable(state) => state, - Toggleable::NotToggleable => ToggleState::NotToggled, - } - } -} - -impl From for ToggleState { - fn from(toggled: bool) -> Self { - if toggled { - ToggleState::Toggled - } else { - ToggleState::NotToggled - } - } -} diff --git a/crates/ui/src/static_data.rs b/crates/ui/src/static_data.rs deleted file mode 100644 index b8c4e18f14..0000000000 --- a/crates/ui/src/static_data.rs +++ /dev/null @@ -1,966 +0,0 @@ -use std::path::PathBuf; -use std::str::FromStr; - -use rand::Rng; - -use crate::{ - Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor, - HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry, - ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, - PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState, - VideoStatus, -}; - -pub fn static_tabs_example() -> Vec { - vec![ - Tab::new() - .title("wip.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("Cargo.toml".to_string()) - .icon(Icon::FileToml) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("Channels Panel".to_string()) - .icon(Icon::Hash) - .current(false), - Tab::new() - .title("channels_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .current(false) - .icon(Icon::FileRust) - .git_status(GitStatus::Modified), - Tab::new() - .title("icon_button.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("storybook.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Created), - Tab::new() - .title("theme.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("theme_registry.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("styleable_helpers.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - ] -} - -pub fn static_tabs_1() -> Vec { - vec![ - Tab::new() - .title("project_panel.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .git_status(GitStatus::Modified), - Tab::new() - .title("workspace.rs".to_string()) - .icon(Icon::FileRust) - .current(false), - Tab::new() - .title("tab.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_2() -> Vec { - vec![ - Tab::new() - .title("tab_bar.rs".to_string()) - .icon(Icon::FileRust) - .current(false) - .fs_status(FileSystemStatus::Deleted), - Tab::new() - .title("static_data.rs".to_string()) - .icon(Icon::FileRust) - .current(true) - .git_status(GitStatus::Modified), - ] -} - -pub fn static_tabs_3() -> Vec { - vec![Tab::new().git_status(GitStatus::Created).current(true)] -} - -pub fn static_players() -> Vec { - vec![ - Player::new( - 0, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "nathansobo".into(), - ), - Player::new( - 1, - "https://avatars.githubusercontent.com/u/326587?v=4".into(), - "maxbrunsfeld".into(), - ), - Player::new( - 2, - "https://avatars.githubusercontent.com/u/482957?v=4".into(), - "as-cii".into(), - ), - Player::new( - 3, - "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - "iamnbutler".into(), - ), - Player::new( - 4, - "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - "maxdeviant".into(), - ), - ] -} - -#[derive(Debug)] -pub struct PlayerData { - pub url: String, - pub name: String, -} -pub fn static_player_data() -> Vec { - vec![ - PlayerData { - url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(), - name: "iamnbutler".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/326587?v=4".into(), - name: "maxbrunsfeld".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/482957?v=4".into(), - name: "as-cii".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1789?v=4".into(), - name: "nathansobo".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "ForLoveOfCats".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(), - name: "SomeoneToIgnore".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(), - name: "JosephTLyons".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(), - name: "osiewicz".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(), - name: "KCaverly".into(), - }, - PlayerData { - url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(), - name: "maxdeviant".into(), - }, - ] -} -pub fn create_static_players(player_data: Vec) -> Vec { - let mut players = Vec::new(); - for data in player_data { - players.push(Player::new(players.len(), data.url, data.name)); - } - players -} -pub fn static_player_1(data: &Vec) -> Player { - Player::new(1, data[0].url.clone(), data[0].name.clone()) -} -pub fn static_player_2(data: &Vec) -> Player { - Player::new(2, data[1].url.clone(), data[1].name.clone()) -} -pub fn static_player_3(data: &Vec) -> Player { - Player::new(3, data[2].url.clone(), data[2].name.clone()) -} -pub fn static_player_4(data: &Vec) -> Player { - Player::new(4, data[3].url.clone(), data[3].name.clone()) -} -pub fn static_player_5(data: &Vec) -> Player { - Player::new(5, data[4].url.clone(), data[4].name.clone()) -} -pub fn static_player_6(data: &Vec) -> Player { - Player::new(6, data[5].url.clone(), data[5].name.clone()) -} -pub fn static_player_7(data: &Vec) -> Player { - Player::new(7, data[6].url.clone(), data[6].name.clone()) -} -pub fn static_player_8(data: &Vec) -> Player { - Player::new(8, data[7].url.clone(), data[7].name.clone()) -} -pub fn static_player_9(data: &Vec) -> Player { - Player::new(9, data[8].url.clone(), data[8].name.clone()) -} -pub fn static_player_10(data: &Vec) -> Player { - Player::new(10, data[9].url.clone(), data[9].name.clone()) -} -pub fn static_livestream() -> Livestream { - Livestream { - players: random_players_with_call_status(7), - channel: Some("gpui2-ui".to_string()), - } -} -pub fn populate_player_call_status( - player: Player, - followers: Option>, -) -> PlayerCallStatus { - let mut rng = rand::thread_rng(); - let in_current_project: bool = rng.gen(); - let disconnected: bool = rng.gen(); - let voice_activity: f32 = rng.gen(); - let mic_status = if rng.gen_bool(0.5) { - MicStatus::Muted - } else { - MicStatus::Unmuted - }; - let video_status = if rng.gen_bool(0.5) { - VideoStatus::On - } else { - VideoStatus::Off - }; - let screen_share_status = if rng.gen_bool(0.5) { - ScreenShareStatus::Shared - } else { - ScreenShareStatus::NotShared - }; - PlayerCallStatus { - mic_status, - voice_activity, - video_status, - screen_share_status, - in_current_project, - disconnected, - following: None, - followers, - } -} -pub fn random_players_with_call_status(number_of_players: usize) -> Vec { - let players = create_static_players(static_player_data()); - let mut player_status = vec![]; - for i in 0..number_of_players { - let followers = if i == 0 { - Some(vec![ - players[1].clone(), - players[3].clone(), - players[5].clone(), - players[6].clone(), - ]) - } else if i == 1 { - Some(vec![players[2].clone(), players[6].clone()]) - } else { - None - }; - let call_status = populate_player_call_status(players[i].clone(), followers); - player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status)); - } - player_status -} - -pub fn static_players_with_call_status() -> Vec { - let players = static_players(); - let mut player_0_status = PlayerCallStatus::new(); - let player_1_status = PlayerCallStatus::new(); - let player_2_status = PlayerCallStatus::new(); - let mut player_3_status = PlayerCallStatus::new(); - let mut player_4_status = PlayerCallStatus::new(); - - player_0_status.screen_share_status = ScreenShareStatus::Shared; - player_0_status.followers = Some(vec![players[1].clone(), players[3].clone()]); - - player_3_status.voice_activity = 0.5; - player_4_status.mic_status = MicStatus::Muted; - player_4_status.in_current_project = false; - - vec![ - PlayerWithCallStatus::new(players[0].clone(), player_0_status), - PlayerWithCallStatus::new(players[1].clone(), player_1_status), - PlayerWithCallStatus::new(players[2].clone(), player_2_status), - PlayerWithCallStatus::new(players[3].clone(), player_3_status), - PlayerWithCallStatus::new(players[4].clone(), player_4_status), - ] -} - -pub fn static_project_panel_project_items() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(0) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".config")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".git").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".cargo")) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".idea").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("assets")) - .left_icon(Icon::Folder.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new("crates")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("activity_indicator")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("ai")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("audio")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("auto_update")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("breadcrumbs")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("call")) - .left_icon(Icon::Folder.into()) - .indent_level(2), - ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::NotToggled), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src")) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("derive_element.rs")) - .left_icon(Icon::FileRust.into()) - .indent_level(4), - ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(1) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("docs").color(LabelColor::Default)) - .left_icon(Icon::Folder.into()) - .indent_level(2) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("src").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(3) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("ui").color(LabelColor::Modified)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(4) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("component").color(LabelColor::Created)) - .left_icon(Icon::FolderOpen.into()) - .indent_level(5) - .set_toggle(ToggleState::Toggled), - ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("follow_group.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("list_item.rs").color(LabelColor::Created)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("tab.rs").color(LabelColor::Default)) - .left_icon(Icon::FileRust.into()) - .indent_level(6), - ListEntry::new(Label::new("target").color(LabelColor::Hidden)) - .left_icon(Icon::Folder.into()) - .indent_level(1), - ListEntry::new(Label::new(".dockerignore")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new(".DS_Store").color(LabelColor::Hidden)) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.lock")) - .left_icon(Icon::FileLock.into()) - .indent_level(1), - ListEntry::new(Label::new("Cargo.toml")) - .left_icon(Icon::FileToml.into()) - .indent_level(1), - ListEntry::new(Label::new("Dockerfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("Procfile")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(1), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(1), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_project_panel_single_items() -> Vec { - vec![ - ListEntry::new(Label::new("todo.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("README.md")) - .left_icon(Icon::FileDoc.into()) - .indent_level(0), - ListEntry::new(Label::new("config.json")) - .left_icon(Icon::FileGeneric.into()) - .indent_level(0), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_current_call() -> Vec { - vec![ - ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"), - ListEntry::new(Label::new("nathansobo")) - .left_avatar("http://github.com/nathansobo.png?s=50"), - ListEntry::new(Label::new("maxbrunsfeld")) - .left_avatar("http://github.com/maxbrunsfeld.png?s=50"), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn static_collab_panel_channels() -> Vec { - vec![ - ListEntry::new(Label::new("zed")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(0), - ListEntry::new(Label::new("community")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("dashboards")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("feedback")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("teams-in-channels-alpha")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("current-projects")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(1), - ListEntry::new(Label::new("codegen")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("gpui2")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("livestreaming")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("open-source")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("replace")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("semantic-index")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("vim")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ListEntry::new(Label::new("web-tech")) - .left_icon(Icon::Hash.into()) - .size(ListEntrySize::Medium) - .indent_level(2), - ] - .into_iter() - .map(From::from) - .collect() -} - -pub fn example_editor_actions() -> Vec { - vec![ - PaletteItem::new("New File").keybinding(Keybinding::new( - "N".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Open File").keybinding(Keybinding::new( - "O".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Save File").keybinding(Keybinding::new( - "S".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Cut").keybinding(Keybinding::new( - "X".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Copy").keybinding(Keybinding::new( - "C".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Paste").keybinding(Keybinding::new( - "V".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Undo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Redo").keybinding(Keybinding::new( - "Z".to_string(), - ModifierKeys::new().control(true).shift(true), - )), - PaletteItem::new("Find").keybinding(Keybinding::new( - "F".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Replace").keybinding(Keybinding::new( - "R".to_string(), - ModifierKeys::new().control(true), - )), - PaletteItem::new("Jump to Line"), - PaletteItem::new("Select All"), - PaletteItem::new("Deselect All"), - PaletteItem::new("Switch Document"), - PaletteItem::new("Insert Line Below"), - PaletteItem::new("Insert Line Above"), - PaletteItem::new("Move Line Up"), - PaletteItem::new("Move Line Down"), - PaletteItem::new("Toggle Comment"), - PaletteItem::new("Delete Line"), - ] -} - -pub fn empty_editor_example() -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![], - buffer: empty_buffer_example(), - } -} - -pub fn empty_buffer_example() -> Buffer { - Buffer::new().set_rows(Some(BufferRows::default())) -} - -pub fn hello_world_rust_editor_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_example(theme), - } -} - -pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} - -pub fn hello_world_rust_editor_with_status_example(theme: &Theme) -> Editor { - Editor { - tabs: static_tabs_example(), - path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), - symbols: vec![Symbol(vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - ])], - buffer: hello_world_rust_buffer_with_status_example(theme), - } -} - -pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("hello_world.rs".to_string()) - .set_path("src/hello_world.rs".to_string()) - .set_language("rust".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: true, - rows: hello_world_rust_with_status_buffer_rows(theme), - })) -} - -pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = true; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: true, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "fn ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "main".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "() {".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Statements here are executed when the compiled binary is called." - .to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Modified, - show_line_number, - }, - BufferRow { - line_number: 3, - code_action: false, - current: false, - line: None, - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 4, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 5, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: " println!(".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "\"Hello, world!\"".to_string(), - color: HighlightColor::String.hsla(&theme), - }, - HighlightedText { - text: ");".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 6, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "}".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 7, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "".to_string(), - color: HighlightColor::Default.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - BufferRow { - line_number: 8, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "// Marshall and Nate were here".to_string(), - color: HighlightColor::Comment.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::Created, - show_line_number, - }, - ] -} - -pub fn terminal_buffer(theme: &Theme) -> Buffer { - Buffer::new() - .set_title("zed — fish".to_string()) - .set_rows(Some(BufferRows { - show_line_numbers: false, - rows: terminal_buffer_rows(theme), - })) -} - -pub fn terminal_buffer_rows(theme: &Theme) -> Vec { - let show_line_number = false; - - vec![ - BufferRow { - line_number: 1, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![ - HighlightedText { - text: "maxdeviant ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "profaned-capital ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "in ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: "~/p/zed ".to_string(), - color: HighlightColor::Function.hsla(&theme), - }, - HighlightedText { - text: "on ".to_string(), - color: HighlightColor::Default.hsla(&theme), - }, - HighlightedText { - text: " gpui2-ui ".to_string(), - color: HighlightColor::Keyword.hsla(&theme), - }, - ], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - BufferRow { - line_number: 2, - code_action: false, - current: false, - line: Some(HighlightedLine { - highlighted_texts: vec![HighlightedText { - text: "λ ".to_string(), - color: HighlightColor::String.hsla(&theme), - }], - }), - cursors: None, - status: GitStatus::None, - show_line_number, - }, - ] -} diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs deleted file mode 100644 index d4c8c26273..0000000000 --- a/crates/ui/src/theme.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::collections::HashMap; -use std::fmt; -use std::marker::PhantomData; -use std::sync::Arc; - -use gpui2::color::Hsla; -use gpui2::element::Element; -use gpui2::{serde_json, AppContext, IntoElement, Vector2F, ViewContext, WindowContext}; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; -use theme::ThemeSettings; - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Theme { - pub name: String, - pub is_light: bool, - pub lowest: Layer, - pub middle: Layer, - pub highest: Layer, - pub popover_shadow: Shadow, - pub modal_shadow: Shadow, - #[serde(deserialize_with = "deserialize_player_colors")] - pub players: Vec, - #[serde(deserialize_with = "deserialize_syntax_colors")] - pub syntax: HashMap, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Layer { - pub base: StyleSet, - pub variant: StyleSet, - pub on: StyleSet, - pub accent: StyleSet, - pub positive: StyleSet, - pub warning: StyleSet, - pub negative: StyleSet, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct StyleSet { - #[serde(rename = "default")] - pub default: ContainerColors, - pub hovered: ContainerColors, - pub pressed: ContainerColors, - pub active: ContainerColors, - pub disabled: ContainerColors, - pub inverted: ContainerColors, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct ContainerColors { - pub background: Hsla, - pub foreground: Hsla, - pub border: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct PlayerColors { - pub selection: Hsla, - pub cursor: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Shadow { - pub blur: u8, - pub color: Hsla, - pub offset: Vec, -} - -fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct PlayerArrayVisitor; - - impl<'de> Visitor<'de> for PlayerArrayVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an object with integer keys") - } - - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut players = Vec::with_capacity(8); - while let Some((key, value)) = map.next_entry::()? { - if key < 8 { - players.push(value); - } else { - return Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(key as u64), - &"a key in range 0..7", - )); - } - } - Ok(players) - } - } - - deserializer.deserialize_map(PlayerArrayVisitor) -} - -fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - struct ColorWrapper { - color: Hsla, - } - - struct SyntaxVisitor; - - impl<'de> Visitor<'de> for SyntaxVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map with keys and objects with a single color field as values") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: serde::de::MapAccess<'de>, - { - let mut result = HashMap::new(); - while let Some(key) = map.next_key()? { - let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla - result.insert(key, wrapper.color); - } - Ok(result) - } - } - deserializer.deserialize_map(SyntaxVisitor) -} - -#[derive(IntoElement)] -pub struct Themed> { - pub(crate) theme: Theme, - pub(crate) child: E, - pub(crate) view_type: PhantomData, -} - -impl> Element for Themed { - type PaintState = E::PaintState; - - fn layout( - &mut self, - view: &mut V, - cx: &mut ViewContext, - ) -> anyhow::Result<(gpui2::LayoutId, Self::PaintState)> - where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - let result = self.child.layout(view, cx); - cx.pop_theme(); - result - } - - fn paint( - &mut self, - view: &mut V, - parent_origin: Vector2F, - layout: &gpui2::Layout, - state: &mut Self::PaintState, - cx: &mut ViewContext, - ) where - Self: Sized, - { - cx.push_theme(self.theme.clone()); - self.child.paint(view, parent_origin, layout, state, cx); - cx.pop_theme(); - } -} - -fn preferred_theme(cx: &AppContext) -> Theme { - settings::get::(cx) - .theme - .deserialized_base_theme - .lock() - .get_or_insert_with(|| { - let theme: Theme = - serde_json::from_value(settings::get::(cx).theme.base_theme.clone()) - .unwrap(); - Box::new(theme) - }) - .downcast_ref::() - .unwrap() - .clone() -} - -pub fn theme(cx: &WindowContext) -> Arc { - cx.theme::() -} diff --git a/crates/ui/src/tokens.rs b/crates/ui/src/tokens.rs deleted file mode 100644 index 5fd5b69a2a..0000000000 --- a/crates/ui/src/tokens.rs +++ /dev/null @@ -1,25 +0,0 @@ -use gpui2::geometry::AbsoluteLength; -use gpui2::{hsla, Hsla}; - -#[derive(Clone, Copy)] -pub struct Token { - pub list_indent_depth: AbsoluteLength, - pub default_panel_size: AbsoluteLength, - pub state_hover_background: Hsla, - pub state_active_background: Hsla, -} - -impl Default for Token { - fn default() -> Self { - Self { - list_indent_depth: AbsoluteLength::Rems(0.5), - default_panel_size: AbsoluteLength::Rems(16.), - state_hover_background: hsla(0.0, 0.0, 0.0, 0.08), - state_active_background: hsla(0.0, 0.0, 0.0, 0.16), - } - } -} - -pub fn token() -> Token { - Token::default() -} diff --git a/crates/ui/tracker.md b/crates/ui/tracker.md deleted file mode 100644 index 11b5804db2..0000000000 --- a/crates/ui/tracker.md +++ /dev/null @@ -1,133 +0,0 @@ -* = Not in the app today - -## Template -- [ ] Workspace -- [ ] Title Bar -- [ ] Project Panel -- [ ] Collab Panel -- [ ] Project Diagnosics -- [ ] Project Search -- [ ] Feedback Editor -- [ ] Terminal -- [ ] Assistant -- [ ] Chat* -- [ ] Notifications* -- [ ] Status Bar -- [ ] Panes -- [ ] Pane -- [ ] Editor -- [ ] Tab Bar -- [ ] Tool Bar -- [ ] Buffer -- [ ] Zoomed Editor (Modal) - -### Palettes -- [ ] Project Files Palette (⌘-P) -- [ ] Command Palette (⌘-SHIFT-P) -- [ ] Recent Projects Palette (⌘-OPT-O) -- [ ] Recent Branches Palette (⌘-OPT-B) -- [ ] Project Symbols (⌘-T) -- [ ] Theme Palette (⌘-K, ⌘-T) -- [ ] Outline View (⌘-SHIFT-O) - -### Debug Views -- [ ] LSP Tool -- [ ] Syntax Tree - -## Modules - -### Title Bar -- [ ] Traffic Lights -- [ ] Host Menu -- [ ] Project Menu -- [ ] Branch Menu -- [ ] Collaborators -- [ ] Add Collaborator* -- [ ] Project Controls -- [ ] Call Controls -- [ ] User Menu - -### Project Panel -- [ ] Open Editors* -- [ ] Open Files (Non-project files) -- [ ] Project Files -- [ ] Root Folder - Context Menu -- [ ] Folder - Context Menu -- [ ] File - Context Menu -- [ ] Project Filter* - -### Collab Panel -- [ ] Current Call -- [ ] Channels -- [ ] Channel - Context Menu -- [ ] Contacts -- [ ] Collab Filter - -### Project Diagnosics -WIP - -### Feedback Editor -- [ ] Feedback Header -- [ ] Editor -- [ ] Feedback Actions - -### Terminal -- [ ] Terminal Toolbar* -- [ ] Terminal Line -- [ ] Terminal Input - -### Assistant -- [ ] Toolbar -- [ ] History / Past Conversations -- [ ] Model Controls / Token Counter -- [ ] Chat Editor - -### Chat -WIP - -### Notifications -WIP - -### Status Bar -- [ ] Status Bar Tool (Icon) -- [ ] Status Bar Tool (Text) -- [ ] Status Bar Tool - Context Menu -- [ ] Status Bar Tool - Popover Palette -- [ ] Status Bar Tool - Popover Menu -- [ ] Diagnostic Message -- [ ] LSP Message -- [ ] Update message (New version available, downloading, etc) - -### Panes/Pane - -- [ ] Editor -- [ ] Split Divider/Control - -### Editor -- [ ] Editor -- [ ] Read-only Editor -- [ ] Rendered Markdown View* - -### Tab Bar -- [ ] Navigation History / Control -- [ ] Tabs -- [ ] Editor Controls (New, Split, Zoom) - -### Tool Bar -- [ ] Breadcrumb -- [ ] Editor Tool (Togglable) -- [ ] Buffer Search - -### Buffer - -### Zoomed Editor (Modal) -- [ ] Modal View - -### Palette -- [ ] Input -- [ ] Section Title -- [ ] List - -## Components - -- [ ] Context Menu From c4fc9f7ed81e6f0199c24ed99a60a64dd1eb98cb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 19:28:17 -0400 Subject: [PATCH 134/141] Eagerly attempt to resolve missing completion documentation --- crates/editor/src/editor.rs | 264 ++++++++++++++++++++++++++---------- 1 file changed, 191 insertions(+), 73 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d7ef82da36..bdacf0be38 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,7 +25,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; -use client::{ClickhouseEvent, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::{Global, ReplicaId}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -62,8 +62,8 @@ use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, - IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, - SelectionGoal, TransactionId, + IndentSize, Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, + Selection, SelectionGoal, TransactionId, }; use link_go_to_definition::{ hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight, @@ -954,7 +954,7 @@ impl CompletionsMenu { ) { self.selected_item = 0; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -967,7 +967,7 @@ impl CompletionsMenu { self.selected_item -= 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -980,7 +980,7 @@ impl CompletionsMenu { self.selected_item += 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); } - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -991,16 +991,99 @@ impl CompletionsMenu { ) { self.selected_item = self.matches.len() - 1; self.list.scroll_to(ScrollTarget::Show(self.selected_item)); - self.attempt_resolve_selected_completion(project, cx); + self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } - fn attempt_resolve_selected_completion( + fn pre_resolve_completion_documentation( + &self, + project: Option>, + cx: &mut ViewContext, + ) { + let Some(project) = project else { + return; + }; + let client = project.read(cx).client(); + let language_registry = project.read(cx).languages().clone(); + + let is_remote = project.read(cx).is_remote(); + let project_id = project.read(cx).remote_id(); + + let completions = self.completions.clone(); + let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect(); + + 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) + }); + 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()); + } + } + }) + .detach(); + } + + fn attempt_resolve_selected_completion_documentation( &mut self, project: Option<&ModelHandle>, cx: &mut ViewContext, ) { - let index = self.matches[self.selected_item].candidate_id; + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; }; @@ -1008,7 +1091,7 @@ impl CompletionsMenu { let completions = self.completions.clone(); let completions_guard = completions.read(); - let completion = &completions_guard[index]; + let completion = &completions_guard[completion_index]; if completion.documentation.is_some() { return; } @@ -1024,54 +1107,95 @@ impl CompletionsMenu { }; let client = project.read(cx).client(); - let request = proto::ResolveCompletionDocumentation { - project_id, - language_server_id: server_id.0 as u64, - lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), - }; - cx.spawn(|this, mut cx| async move { - 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[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[index]; - completion.documentation = Some(documentation); - drop(completions); + cx.spawn(move |this, mut cx| async move { + Self::resolve_completion_documentation_remote( + project_id, + server_id, + completions.clone(), + completion_index, + completion, + client, + language_registry.clone(), + ) + .await; _ = this.update(&mut cx, |_, cx| cx.notify()); }) .detach(); + } else { + let Some(server) = project.read(cx).language_server_for_id(server_id) else { + return; + }; - 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(); } + } - let Some(server) = project.read(cx).language_server_for_id(server_id) else { + async fn resolve_completion_documentation_remote( + project_id: u64, + server_id: LanguageServerId, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + client: Arc, + language_registry: Arc, + ) { + 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, + completions: Arc>>, + completion_index: usize, + completion: lsp::CompletionItem, + language_registry: Arc, + ) { let can_resolve = server .capabilities() .completion_provider @@ -1082,33 +1206,27 @@ impl CompletionsMenu { return; } - cx.spawn(|this, mut cx| async move { - let request = server.request::(completion); - let Some(completion_item) = request.await.log_err() else { - return; - }; + let request = server.request::(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; + 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[index]; - completion.documentation = Some(documentation); - drop(completions); - - _ = this.update(&mut cx, |_, cx| cx.notify()); - } else { - let mut completions = completions.write(); - let completion = &mut completions[index]; - completion.documentation = Some(Documentation::Undocumented); - } - }) - .detach(); + 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 { @@ -3450,7 +3568,7 @@ impl Editor { None } else { _ = this.update(&mut cx, |editor, cx| { - menu.attempt_resolve_selected_completion(editor.project.as_ref(), cx); + menu.pre_resolve_completion_documentation(editor.project.clone(), cx); }); Some(menu) } From 1c3ecc4ad242047a700a602ceda0b3630b7118d0 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 21:00:31 -0400 Subject: [PATCH 135/141] Whooooops --- crates/editor/src/editor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdacf0be38..1a17f38f92 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3596,7 +3596,6 @@ impl Editor { let menu = menu.unwrap(); *context_menu = Some(ContextMenu::Completions(menu)); drop(context_menu); - this.completion_tasks.clear(); this.discard_copilot_suggestion(cx); cx.notify(); } else if this.completion_tasks.is_empty() { From ec4391b88e9a41e47de295896cb20764f007e053 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 12 Oct 2023 22:08:47 -0400 Subject: [PATCH 136/141] Add setting to disable completion docs --- assets/settings/default.json | 3 +++ crates/editor/src/editor.rs | 24 ++++++++++++++++++++++-- crates/editor/src/editor_settings.rs | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 8fb73a2ecb..8a3598eed1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -50,6 +50,9 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Whether to display inline and alongside documentation for items in the + // completions menu + "show_completion_documentation": true, // Whether to show wrap guides in the editor. Setting this to true will // show a guide at the 'preferred_line_length' value if softwrap is set to // 'preferred_line_length', and will show any additional guides as specified diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1a17f38f92..bb6d693d82 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1000,6 +1000,11 @@ impl CompletionsMenu { project: Option>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let Some(project) = project else { return; }; @@ -1083,6 +1088,11 @@ impl CompletionsMenu { project: Option<&ModelHandle>, cx: &mut ViewContext, ) { + let settings = settings::get::(cx); + if !settings.show_completion_documentation { + return; + } + let completion_index = self.matches[self.selected_item].candidate_id; let Some(project) = project else { return; @@ -1241,6 +1251,9 @@ impl CompletionsMenu { ) -> AnyElement { enum CompletionTag {} + let settings = settings::get::(cx); + let show_completion_documentation = settings.show_completion_documentation; + let widest_completion_ix = self .matches .iter() @@ -1252,7 +1265,9 @@ impl CompletionsMenu { let mut len = completion.label.text.chars().count(); if let Some(Documentation::SingleLine(text)) = documentation { - len += text.chars().count(); + if show_completion_documentation { + len += text.chars().count(); + } } len @@ -1273,7 +1288,12 @@ impl CompletionsMenu { let item_ix = start_ix + ix; let candidate_id = mat.candidate_id; let completion = &completions_guard[candidate_id]; - let documentation = &completion.documentation; + + let documentation = if show_completion_documentation { + &completion.documentation + } else { + &None + }; items.push( MouseEventHandler::new::( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index b06f23429a..75f8b800f9 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -7,6 +7,7 @@ pub struct EditorSettings { pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, + pub show_completion_documentation: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, pub relative_line_numbers: bool, @@ -33,6 +34,7 @@ pub struct EditorSettingsContent { pub cursor_blink: Option, pub hover_popover_enabled: Option, pub show_completions_on_input: Option, + pub show_completion_documentation: Option, pub use_on_type_format: Option, pub scrollbar: Option, pub relative_line_numbers: Option, From 525ff6bf7458a2f48747fc9e06dd10e83845554b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 10:20:55 +0300 Subject: [PATCH 137/141] Remove zed -> ... -> semantic_index -> zed Cargo dependency cycle --- Cargo.lock | 2 +- crates/semantic_index/Cargo.toml | 4 ---- crates/zed/Cargo.toml | 4 ++++ .../examples/eval.rs => zed/examples/semantic_index_eval.rs} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename crates/{semantic_index/examples/eval.rs => zed/examples/semantic_index_eval.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 01153ca0f8..85b80fa487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6956,7 +6956,6 @@ dependencies = [ "unindent", "util", "workspace", - "zed", ] [[package]] @@ -10049,6 +10048,7 @@ name = "zed" version = "0.109.0" dependencies = [ "activity_indicator", + "ai", "anyhow", "assistant", "async-compression", diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 34850f7035..1febb2af78 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -51,7 +51,6 @@ workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} rust-embed = { version = "8.0", features = ["include-exclude"] } client = { path = "../client" } -zed = { path = "../zed"} node_runtime = { path = "../node_runtime"} pretty_assertions.workspace = true @@ -70,6 +69,3 @@ tree-sitter-elixir.workspace = true tree-sitter-lua.workspace = true tree-sitter-ruby.workspace = true tree-sitter-php.workspace = true - -[[example]] -name = "eval" diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4174f7d6d5..f9abcc1e91 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -15,6 +15,9 @@ doctest = false name = "Zed" path = "src/main.rs" +[[example]] +name = "semantic_index_eval" + [dependencies] audio = { path = "../audio" } activity_indicator = { path = "../activity_indicator" } @@ -141,6 +144,7 @@ urlencoding = "2.1.2" uuid.workspace = true [dev-dependencies] +ai = { path = "../ai" } call = { path = "../call", features = ["test-support"] } client = { path = "../client", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/semantic_index/examples/eval.rs b/crates/zed/examples/semantic_index_eval.rs similarity index 100% rename from crates/semantic_index/examples/eval.rs rename to crates/zed/examples/semantic_index_eval.rs From 803ab81eb6e1cc718679fdba862163c23d7ef174 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 13 Oct 2023 12:13:18 +0300 Subject: [PATCH 138/141] Update diagnostics indicator when diagnostics are udpated --- crates/diagnostics/src/items.rs | 4 ++++ crates/project/src/project.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index c3733018b6..8d3c2fedd6 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -38,6 +38,10 @@ impl DiagnosticIndicator { this.in_progress_checks.remove(language_server_id); cx.notify(); } + project::Event::DiagnosticsUpdated { .. } => { + this.summary = project.read(cx).diagnostic_summary(cx); + cx.notify(); + } _ => {} }) .detach(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f9e1b1ce96..e3251d7483 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2934,8 +2934,8 @@ impl Project { move |mut params, mut cx| { let this = this; let adapter = adapter.clone(); - adapter.process_diagnostics(&mut params); if let Some(this) = this.upgrade(&cx) { + adapter.process_diagnostics(&mut params); this.update(&mut cx, |this, cx| { this.update_diagnostics( server_id, From bfbe4ae4b47140024cdaf8d9680956bd228d6b84 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:58:59 +0200 Subject: [PATCH 139/141] Piotr/z 651 vue support (#3123) Release Notes: - Added Vue language support. --- Cargo.lock | 10 + Cargo.toml | 2 +- crates/language/src/language.rs | 3 - crates/project/src/project.rs | 25 ++- crates/util/src/github.rs | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 10 +- crates/zed/src/languages/vue.rs | 214 ++++++++++++++++++++ crates/zed/src/languages/vue/brackets.scm | 2 + crates/zed/src/languages/vue/config.toml | 14 ++ crates/zed/src/languages/vue/highlights.scm | 15 ++ crates/zed/src/languages/vue/injections.scm | 7 + 12 files changed, 286 insertions(+), 18 deletions(-) create mode 100644 crates/zed/src/languages/vue.rs create mode 100644 crates/zed/src/languages/vue/brackets.scm create mode 100644 crates/zed/src/languages/vue/config.toml create mode 100644 crates/zed/src/languages/vue/highlights.scm create mode 100644 crates/zed/src/languages/vue/injections.scm diff --git a/Cargo.lock b/Cargo.lock index 85b80fa487..2ef86073ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8792,6 +8792,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-vue" +version = "0.0.1" +source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=95b2890#95b28908d90e928c308866f7631e73ef6e1d4b5f" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-yaml" version = "0.0.1" @@ -10161,6 +10170,7 @@ dependencies = [ "tree-sitter-svelte", "tree-sitter-toml", "tree-sitter-typescript", + "tree-sitter-vue", "tree-sitter-yaml", "unindent", "url", diff --git a/Cargo.toml b/Cargo.toml index 532610efd6..995cd15edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,7 +149,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} - +tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "95b2890"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bd389652a0..eb6f6e89f7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -110,7 +110,6 @@ pub struct LanguageServerName(pub Arc); pub struct CachedLspAdapter { pub name: LanguageServerName, pub short_name: &'static str, - pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub language_ids: HashMap, @@ -121,7 +120,6 @@ impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { let name = adapter.name().await; let short_name = adapter.short_name(); - let initialization_options = adapter.initialization_options().await; let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token().await; @@ -130,7 +128,6 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, short_name, - initialization_options, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e3251d7483..875086a4e3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2751,15 +2751,6 @@ impl Project { let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ @@ -2771,7 +2762,7 @@ impl Project { cx.spawn_weak(|this, mut cx| async move { let result = Self::setup_and_insert_language_server( this, - initialization_options, + override_options, pending_server, adapter.clone(), language.clone(), @@ -2874,7 +2865,7 @@ impl Project { async fn setup_and_insert_language_server( this: WeakModelHandle, - initialization_options: Option, + override_initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, language: Arc, @@ -2884,7 +2875,7 @@ impl Project { ) -> Result>> { let setup = Self::setup_pending_language_server( this, - initialization_options, + override_initialization_options, pending_server, adapter.clone(), server_id, @@ -2916,7 +2907,7 @@ impl Project { async fn setup_pending_language_server( this: WeakModelHandle, - initialization_options: Option, + override_options: Option, pending_server: PendingLanguageServer, adapter: Arc, server_id: LanguageServerId, @@ -3062,6 +3053,14 @@ impl Project { } }) .detach(); + let mut initialization_options = adapter.adapter.initialization_options().await; + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } let language_server = language_server.initialize(initialization_options).await?; diff --git a/crates/util/src/github.rs b/crates/util/src/github.rs index b1e981ae49..a3df4c996b 100644 --- a/crates/util/src/github.rs +++ b/crates/util/src/github.rs @@ -16,6 +16,7 @@ pub struct GithubRelease { pub pre_release: bool, pub assets: Vec, pub tarball_url: String, + pub zipball_url: String, } #[derive(Deserialize, Debug)] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f9abcc1e91..aeabd4b453 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -138,6 +138,7 @@ tree-sitter-yaml.workspace = true tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true tree-sitter-nu.workspace = true +tree-sitter-vue.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 04e5292a7d..caf3cbf7c9 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -24,6 +24,7 @@ mod rust; mod svelte; mod tailwind; mod typescript; +mod vue; mod yaml; // 1. Add tree-sitter-{language} parser to zed crate @@ -190,13 +191,20 @@ pub fn init( language( "php", tree_sitter_php::language(), - vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], + vec![Arc::new(php::IntelephenseLspAdapter::new( + node_runtime.clone(), + ))], ); language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); language("nu", tree_sitter_nu::language(), vec![]); + language( + "vue", + tree_sitter_vue::language(), + vec![Arc::new(vue::VueLspAdapter::new(node_runtime))], + ); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/vue.rs b/crates/zed/src/languages/vue.rs new file mode 100644 index 0000000000..f0374452df --- /dev/null +++ b/crates/zed/src/languages/vue.rs @@ -0,0 +1,214 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::StreamExt; +pub use language::*; +use lsp::{CodeActionKind, LanguageServerBinary}; +use node_runtime::NodeRuntime; +use parking_lot::Mutex; +use serde_json::Value; +use smol::fs::{self}; +use std::{ + any::Any, + ffi::OsString, + path::{Path, PathBuf}, + sync::Arc, +}; +use util::ResultExt; + +pub struct VueLspVersion { + vue_version: String, + ts_version: String, +} + +pub struct VueLspAdapter { + node: Arc, + typescript_install_path: Mutex>, +} + +impl VueLspAdapter { + const SERVER_PATH: &'static str = + "node_modules/@vue/language-server/bin/vue-language-server.js"; + // TODO: this can't be hardcoded, yet we have to figure out how to pass it in initialization_options. + const TYPESCRIPT_PATH: &'static str = "node_modules/typescript/lib"; + pub fn new(node: Arc) -> Self { + let typescript_install_path = Mutex::new(None); + Self { + node, + typescript_install_path, + } + } +} +#[async_trait] +impl super::LspAdapter for VueLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("vue-language-server".into()) + } + + fn short_name(&self) -> &'static str { + "vue-language-server" + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + ) -> Result> { + Ok(Box::new(VueLspVersion { + vue_version: self + .node + .npm_package_latest_version("@vue/language-server") + .await?, + ts_version: self.node.npm_package_latest_version("typescript").await?, + }) as Box<_>) + } + async fn initialization_options(&self) -> Option { + let typescript_sdk_path = self.typescript_install_path.lock(); + let typescript_sdk_path = typescript_sdk_path + .as_ref() + .expect("initialization_options called without a container_dir for typescript"); + + Some(serde_json::json!({ + "typescript": { + "tsdk": typescript_sdk_path + } + })) + } + fn code_action_kinds(&self) -> Option> { + // REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it + // sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec. + Some(vec![ + CodeActionKind::EMPTY, + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR_REWRITE, + ]) + } + async fn fetch_server_binary( + &self, + version: Box, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Result { + let version = version.downcast::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + let ts_path = container_dir.join(Self::TYPESCRIPT_PATH); + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("@vue/language-server", version.vue_version.as_str())], + ) + .await?; + } + assert!(fs::metadata(&server_path).await.is_ok()); + if fs::metadata(&ts_path).await.is_err() { + self.node + .npm_install_packages( + &container_dir, + &[("typescript", version.ts_version.as_str())], + ) + .await?; + } + + assert!(fs::metadata(&ts_path).await.is_ok()); + *self.typescript_install_path.lock() = Some(ts_path); + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()).await?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()) + .await + .map(|(mut binary, ts_path)| { + binary.arguments = vec!["--help".into()]; + (binary, ts_path) + })?; + *self.typescript_install_path.lock() = Some(ts_path); + Some(server) + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + use lsp::CompletionItemKind as Kind; + let len = item.label.len(); + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"), + Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"), + Kind::CONSTANT => grammar.highlight_id_for_name("constant"), + Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"), + Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("tag"), + Kind::VARIABLE => grammar.highlight_id_for_name("type"), + Kind::KEYWORD => grammar.highlight_id_for_name("keyword"), + Kind::VALUE => grammar.highlight_id_for_name("tag"), + _ => None, + }?; + + let text = match &item.detail { + Some(detail) => format!("{} {}", item.label, detail), + None => item.label.clone(), + }; + + Some(language::CodeLabel { + text, + runs: vec![(0..len, highlight_id)], + filter_range: 0..len, + }) + } +} + +fn vue_server_binary_arguments(server_path: &Path) -> Vec { + vec![server_path.into(), "--stdio".into()] +} + +type TypescriptPath = PathBuf; +async fn get_cached_server_binary( + container_dir: PathBuf, + node: Arc, +) -> Option<(LanguageServerBinary, TypescriptPath)> { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(VueLspAdapter::SERVER_PATH); + let typescript_path = last_version_dir.join(VueLspAdapter::TYPESCRIPT_PATH); + if server_path.exists() && typescript_path.exists() { + Ok(( + LanguageServerBinary { + path: node.binary_path().await?, + arguments: vue_server_binary_arguments(&server_path), + }, + typescript_path, + )) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/vue/brackets.scm b/crates/zed/src/languages/vue/brackets.scm new file mode 100644 index 0000000000..2d12b17daa --- /dev/null +++ b/crates/zed/src/languages/vue/brackets.scm @@ -0,0 +1,2 @@ +("<" @open ">" @close) +("\"" @open "\"" @close) diff --git a/crates/zed/src/languages/vue/config.toml b/crates/zed/src/languages/vue/config.toml new file mode 100644 index 0000000000..c41a667b75 --- /dev/null +++ b/crates/zed/src/languages/vue/config.toml @@ -0,0 +1,14 @@ +name = "Vue.js" +path_suffixes = ["vue"] +block_comment = [""] +autoclose_before = ";:.,=}])>" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = true, newline = true, not_in = ["string", "comment"] }, + { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, + { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, + { start = "`", end = "`", close = true, newline = false, not_in = ["string"] }, +] +word_characters = ["-"] diff --git a/crates/zed/src/languages/vue/highlights.scm b/crates/zed/src/languages/vue/highlights.scm new file mode 100644 index 0000000000..1a80c84f68 --- /dev/null +++ b/crates/zed/src/languages/vue/highlights.scm @@ -0,0 +1,15 @@ +(attribute) @property +(directive_attribute) @property +(quoted_attribute_value) @string +(interpolation) @punctuation.special +(raw_text) @embedded + +((tag_name) @type + (#match? @type "^[A-Z]")) + +((directive_name) @keyword + (#match? @keyword "^v-")) + +(start_tag) @tag +(end_tag) @tag +(self_closing_tag) @tag diff --git a/crates/zed/src/languages/vue/injections.scm b/crates/zed/src/languages/vue/injections.scm new file mode 100644 index 0000000000..9084e373f2 --- /dev/null +++ b/crates/zed/src/languages/vue/injections.scm @@ -0,0 +1,7 @@ +(script_element + (raw_text) @content + (#set! "language" "javascript")) + +(style_element + (raw_text) @content + (#set! "language" "css")) From 6f4008ebabd33793cd70a8f042c2c9510680a89b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:27:36 +0200 Subject: [PATCH 140/141] copilot: Propagate action if suggest_next is not possible. (#3129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of our users ran into an issue where typing "true quote" characters (option-[ for „ and option-] for ‚) was not possible; I've narrowed it down to a collision with Copilot's NextSuggestion and PreviousSuggestion action default keybinds. I explicitly did not want to alter the key bindings, so I've went with a more neutral fix - one that propagates the keystroke if there's no Copilot action to be taken (user is not using Copilot etc). Note however that typing true quotes while using a Copilot is still not possible, as for that we'd have to change a keybind. Fixes zed-industries/community#2072 Release Notes: - Fixed Copilot's "Suggest next" and "Suggest previous" actions colliding with true quotes key bindings (`option-[` and `option-]`). The keystrokes are now propagated if there's no Copilot action to be taken at cursor's position. --- crates/editor/src/editor.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb6d693d82..7aca4ab98f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4158,7 +4158,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Next, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } @@ -4170,7 +4173,10 @@ impl Editor { if self.has_active_copilot_suggestion(cx) { self.cycle_copilot_suggestions(Direction::Prev, cx); } else { - self.refresh_copilot_suggestions(false, cx); + let is_copilot_disabled = self.refresh_copilot_suggestions(false, cx).is_none(); + if is_copilot_disabled { + cx.propagate_action(); + } } } From cc335db9e0e7ce677591d544140760ed8c080eec Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:17:44 +0200 Subject: [PATCH 141/141] editor/language: hoist out non-generic parts of edit functions. (#3130) This reduces LLVM IR size of editor (that's one of the heaviest crates to build) by almost 5%. LLVM IR size of `editor` before this PR: 3280386 LLVM IR size with `editor::edit` changed: 3227092 LLVM IR size with `editor::edit` and `language::edit` changed: 3146807 Release Notes: - N/A --- crates/editor/src/multi_buffer.rs | 140 +++++++++++++++------------- crates/language/src/buffer.rs | 149 ++++++++++++++++-------------- 2 files changed, 158 insertions(+), 131 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c5d17dfd2e..23a117405c 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -498,77 +498,91 @@ impl MultiBuffer { } } - for (buffer_id, mut edits) in buffer_edits { - edits.sort_unstable_by_key(|edit| edit.range.start); - self.buffers.borrow()[&buffer_id] - .buffer - .update(cx, |buffer, cx| { - let mut edits = edits.into_iter().peekable(); - let mut insertions = Vec::new(); - let mut original_indent_columns = Vec::new(); - let mut deletions = Vec::new(); - let empty_str: Arc = "".into(); - while let Some(BufferEdit { - mut range, - new_text, - mut is_insertion, - original_indent_column, - }) = edits.next() - { + drop(cursor); + drop(snapshot); + // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR. + fn tail( + this: &mut MultiBuffer, + buffer_edits: HashMap>, + autoindent_mode: Option, + edited_excerpt_ids: Vec, + cx: &mut ModelContext, + ) { + for (buffer_id, mut edits) in buffer_edits { + edits.sort_unstable_by_key(|edit| edit.range.start); + this.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + let mut edits = edits.into_iter().peekable(); + let mut insertions = Vec::new(); + let mut original_indent_columns = Vec::new(); + let mut deletions = Vec::new(); + let empty_str: Arc = "".into(); while let Some(BufferEdit { - range: next_range, - is_insertion: next_is_insertion, - .. - }) = edits.peek() + mut range, + new_text, + mut is_insertion, + original_indent_column, + }) = edits.next() { - if range.end >= next_range.start { - range.end = cmp::max(next_range.end, range.end); - is_insertion |= *next_is_insertion; - edits.next(); - } else { - break; + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { + if range.end >= next_range.start { + range.end = cmp::max(next_range.end, range.end); + is_insertion |= *next_is_insertion; + edits.next(); + } else { + break; + } + } + + if is_insertion { + original_indent_columns.push(original_indent_column); + insertions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + new_text.clone(), + )); + } else if !range.is_empty() { + deletions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + empty_str.clone(), + )); } } - if is_insertion { - original_indent_columns.push(original_indent_column); - insertions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - new_text.clone(), - )); - } else if !range.is_empty() { - deletions.push(( - buffer.anchor_before(range.start)..buffer.anchor_before(range.end), - empty_str.clone(), - )); - } - } + let deletion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns: Default::default(), + }) + } else { + None + }; + let insertion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) + } else { + None + }; - let deletion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns: Default::default(), - }) - } else { - None - }; - let insertion_autoindent_mode = - if let Some(AutoindentMode::Block { .. }) = autoindent_mode { - Some(AutoindentMode::Block { - original_indent_columns, - }) - } else { - None - }; + buffer.edit(deletions, deletion_autoindent_mode, cx); + buffer.edit(insertions, insertion_autoindent_mode, cx); + }) + } - buffer.edit(deletions, deletion_autoindent_mode, cx); - buffer.edit(insertions, insertion_autoindent_mode, cx); - }) + cx.emit(Event::ExcerptsEdited { + ids: edited_excerpt_ids, + }); } - - cx.emit(Event::ExcerptsEdited { - ids: edited_excerpt_ids, - }); + tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx); } pub fn start_transaction(&mut self, cx: &mut ModelContext) -> Option { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d8ebc1d445..78562ba8c4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1448,82 +1448,95 @@ impl Buffer { return None; } - self.start_transaction(); - self.pending_autoindent.take(); - let autoindent_request = autoindent_mode - .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode))); + // Non-generic part hoisted out to reduce LLVM IR size. + fn tail( + this: &mut Buffer, + edits: Vec<(Range, Arc)>, + autoindent_mode: Option, + cx: &mut ModelContext, + ) -> Option { + this.start_transaction(); + this.pending_autoindent.take(); + let autoindent_request = autoindent_mode + .and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode))); - let edit_operation = self.text.edit(edits.iter().cloned()); - let edit_id = edit_operation.timestamp(); + let edit_operation = this.text.edit(edits.iter().cloned()); + let edit_id = edit_operation.timestamp(); - if let Some((before_edit, mode)) = autoindent_request { - let mut delta = 0isize; - let entries = edits - .into_iter() - .enumerate() - .zip(&edit_operation.as_edit().unwrap().new_text) - .map(|((ix, (range, _)), new_text)| { - let new_text_length = new_text.len(); - let old_start = range.start.to_point(&before_edit); - let new_start = (delta + range.start as isize) as usize; - delta += new_text_length as isize - (range.end as isize - range.start as isize); + if let Some((before_edit, mode)) = autoindent_request { + let mut delta = 0isize; + let entries = edits + .into_iter() + .enumerate() + .zip(&edit_operation.as_edit().unwrap().new_text) + .map(|((ix, (range, _)), new_text)| { + let new_text_length = new_text.len(); + let old_start = range.start.to_point(&before_edit); + let new_start = (delta + range.start as isize) as usize; + delta += + new_text_length as isize - (range.end as isize - range.start as isize); - let mut range_of_insertion_to_indent = 0..new_text_length; - let mut first_line_is_new = false; - let mut original_indent_column = None; + let mut range_of_insertion_to_indent = 0..new_text_length; + let mut first_line_is_new = false; + let mut original_indent_column = None; - // When inserting an entire line at the beginning of an existing line, - // treat the insertion as new. - if new_text.contains('\n') - && old_start.column <= before_edit.indent_size_for_line(old_start.row).len - { - first_line_is_new = true; - } - - // When inserting text starting with a newline, avoid auto-indenting the - // previous line. - if new_text.starts_with('\n') { - range_of_insertion_to_indent.start += 1; - first_line_is_new = true; - } - - // Avoid auto-indenting after the insertion. - if let AutoindentMode::Block { - original_indent_columns, - } = &mode - { - original_indent_column = - Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| { - indent_size_for_text( - new_text[range_of_insertion_to_indent.clone()].chars(), - ) - .len - })); - if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { - range_of_insertion_to_indent.end -= 1; + // When inserting an entire line at the beginning of an existing line, + // treat the insertion as new. + if new_text.contains('\n') + && old_start.column + <= before_edit.indent_size_for_line(old_start.row).len + { + first_line_is_new = true; } - } - AutoindentRequestEntry { - first_line_is_new, - original_indent_column, - indent_size: before_edit.language_indent_size_at(range.start, cx), - range: self.anchor_before(new_start + range_of_insertion_to_indent.start) - ..self.anchor_after(new_start + range_of_insertion_to_indent.end), - } - }) - .collect(); + // When inserting text starting with a newline, avoid auto-indenting the + // previous line. + if new_text.starts_with('\n') { + range_of_insertion_to_indent.start += 1; + first_line_is_new = true; + } - self.autoindent_requests.push(Arc::new(AutoindentRequest { - before_edit, - entries, - is_block_mode: matches!(mode, AutoindentMode::Block { .. }), - })); + // Avoid auto-indenting after the insertion. + if let AutoindentMode::Block { + original_indent_columns, + } = &mode + { + original_indent_column = Some( + original_indent_columns.get(ix).copied().unwrap_or_else(|| { + indent_size_for_text( + new_text[range_of_insertion_to_indent.clone()].chars(), + ) + .len + }), + ); + if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { + range_of_insertion_to_indent.end -= 1; + } + } + + AutoindentRequestEntry { + first_line_is_new, + original_indent_column, + indent_size: before_edit.language_indent_size_at(range.start, cx), + range: this + .anchor_before(new_start + range_of_insertion_to_indent.start) + ..this.anchor_after(new_start + range_of_insertion_to_indent.end), + } + }) + .collect(); + + this.autoindent_requests.push(Arc::new(AutoindentRequest { + before_edit, + entries, + is_block_mode: matches!(mode, AutoindentMode::Block { .. }), + })); + } + + this.end_transaction(cx); + this.send_operation(Operation::Buffer(edit_operation), cx); + Some(edit_id) } - - self.end_transaction(cx); - self.send_operation(Operation::Buffer(edit_operation), cx); - Some(edit_id) + tail(self, edits, autoindent_mode, cx) } fn did_edit(