From ec0409a3d1aeaa04d6e2dc5715486d047f53e363 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 7 Jun 2023 14:01:50 -0400 Subject: [PATCH 001/169] Detect LSP startup failure --- crates/language/src/language.rs | 2 +- crates/lsp/src/lsp.rs | 1 + crates/project/src/project.rs | 603 ++++++++++++++++++-------------- 3 files changed, 333 insertions(+), 273 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e91d5770cfba80485f3cbe6272257a81c48c4fc3..fa85d0e21f64561d465511f27084f3899f6f6f47 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -887,8 +887,8 @@ impl LanguageRegistry { }) .clone(); drop(lock); - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; + let binary = entry.clone().map_err(|e| anyhow!(e)).await?; let server = lsp::LanguageServer::new( server_id, &binary.path, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 96d43820758ee0ad17dd8bb11861fc57573f3f17..f05acbb1a2d57d011a82e7b058db8965d1f52045 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -167,6 +167,7 @@ impl LanguageServer { if let Some(name) = binary_path.file_name() { server.name = name.to_string_lossy().to_string(); } + Ok(server) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5069c805b729698b95a0988f56d15417cb2cc0c6..f78ff19eaef366b76f8ea5d1281560057f27bbd6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2383,16 +2383,9 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !language_settings( - Some(&language), - worktree - .update(cx, |tree, cx| tree.root_file(cx)) - .map(|f| f as _) - .as_ref(), - cx, - ) - .enable_language_server - { + let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx)); + let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx); + if !settings.enable_language_server { return; } @@ -2414,9 +2407,8 @@ impl Project { None => continue, }; - let lsp = settings::get::(cx) - .lsp - .get(&adapter.name.0); + let project_settings = settings::get::(cx); + 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(); @@ -2429,302 +2421,368 @@ impl Project { } let server_id = pending_server.server_id; - let state = self.setup_pending_language_server( - initialization_options, - pending_server, - adapter.clone(), - language.clone(), - key.clone(), - cx, - ); + let state = LanguageServerState::Starting({ + let server_name = adapter.name.0.clone(); + let adapter = adapter.clone(); + let language = language.clone(); + let languages = self.languages.clone(); + let key = key.clone(); + + cx.spawn_weak(|this, cx| async move { + let result = Self::setup_and_insert_language_server( + this, + initialization_options, + pending_server, + adapter, + languages, + language, + server_id, + key, + cx, + ) + .await; + + match result { + Ok(server) => Some(server), + + Err(err) => { + log::warn!("Error starting language server {:?}: {}", server_name, err); + // TODO: Prompt installation validity check + None + } + } + }) + }); + self.language_servers.insert(server_id, state); - self.language_server_ids.insert(key.clone(), server_id); + self.language_server_ids.insert(key, server_id); } } - fn setup_pending_language_server( - &mut self, + async fn setup_and_insert_language_server( + this: WeakModelHandle, initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, + languages: Arc, language: Arc, + server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - cx: &mut ModelContext, - ) -> LanguageServerState { - let server_id = pending_server.server_id; - let languages = self.languages.clone(); + mut cx: AsyncAppContext, + ) -> Result> { + let language_server = Self::setup_pending_language_server( + this, + initialization_options, + pending_server, + adapter.clone(), + languages, + server_id, + &mut cx, + ) + .await?; - LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { - let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = pending_server.task.await.log_err()?; + let this = match this.upgrade(&mut cx) { + Some(this) => this, + None => return Err(anyhow!("failed to upgrade project handle")), + }; - language_server - .on_notification::({ - move |params, mut cx| { + this.update(&mut cx, |this, cx| { + this.insert_newly_running_language_server( + language, + adapter, + language_server.clone(), + server_id, + key, + cx, + ) + })?; + + Ok(language_server) + } + + async fn setup_pending_language_server( + this: WeakModelHandle, + initialization_options: Option, + pending_server: PendingLanguageServer, + adapter: Arc, + languages: Arc, + server_id: LanguageServerId, + cx: &mut AsyncAppContext, + ) -> Result> { + let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; + + let language_server = pending_server.task.await?; + let language_server = language_server.initialize(initialization_options).await?; + + language_server + .on_notification::({ + move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| { + cx.emit(Event::LanguageServerLog(server_id, params.message)) + }); + } + } + }) + .detach(); + + language_server + .on_notification::({ + let adapter = adapter.clone(); + move |mut params, cx| { + let this = this; + let adapter = adapter.clone(); + cx.spawn(|mut cx| async move { + adapter.process_diagnostics(&mut params).await; if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| { - cx.emit(Event::LanguageServerLog(server_id, params.message)) + this.update(&mut cx, |this, cx| { + this.update_diagnostics( + server_id, + params, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); }); } + }) + .detach(); + } + }) + .detach(); + + language_server + .on_request::({ + let languages = languages.clone(); + move |params, mut cx| { + let languages = languages.clone(); + async move { + let workspace_config = + cx.update(|cx| languages.workspace_configuration(cx)).await; + Ok(params + .items + .into_iter() + .map(|item| { + if let Some(section) = &item.section { + workspace_config + .get(section) + .cloned() + .unwrap_or(serde_json::Value::Null) + } else { + workspace_config.clone() + } + }) + .collect()) } - }) - .detach(); + } + }) + .detach(); - language_server - .on_notification::({ - let adapter = adapter.clone(); - move |mut params, cx| { - let adapter = adapter.clone(); - cx.spawn(|mut cx| async move { - adapter.process_diagnostics(&mut params).await; - if let Some(this) = this.upgrade(&cx) { + // Even though we don't have handling for these requests, respond to them to + // avoid stalling any language server like `gopls` which waits for a response + // to these requests when initializing. + language_server + .on_request::( + move |params, mut cx| async move { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| { + if let Some(status) = this.language_server_statuses.get_mut(&server_id) + { + if let lsp::NumberOrString::String(token) = params.token { + status.progress_tokens.insert(token); + } + } + }); + } + Ok(()) + }, + ) + .detach(); + language_server + .on_request::({ + move |params, mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + for reg in params.registrations { + if reg.method == "workspace/didChangeWatchedFiles" { + if let Some(options) = reg.register_options { + let options = serde_json::from_value(options)?; this.update(&mut cx, |this, cx| { - this.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); + this.on_lsp_did_change_watched_files(server_id, options, cx); }); } - }) - .detach(); - } - }) - .detach(); - - language_server - .on_request::({ - let languages = languages.clone(); - move |params, mut cx| { - let languages = languages.clone(); - async move { - let workspace_config = - cx.update(|cx| languages.workspace_configuration(cx)).await; - Ok(params - .items - .into_iter() - .map(|item| { - if let Some(section) = &item.section { - workspace_config - .get(section) - .cloned() - .unwrap_or(serde_json::Value::Null) - } else { - workspace_config.clone() - } - }) - .collect()) } } - }) - .detach(); + Ok(()) + } + }) + .detach(); - // Even though we don't have handling for these requests, respond to them to - // avoid stalling any language server like `gopls` which waits for a response - // to these requests when initializing. - language_server - .on_request::( - move |params, mut cx| async move { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, _| { - if let Some(status) = - this.language_server_statuses.get_mut(&server_id) - { - if let lsp::NumberOrString::String(token) = params.token { - status.progress_tokens.insert(token); - } - } - }); - } - Ok(()) - }, - ) - .detach(); - language_server - .on_request::( - move |params, mut cx| async move { - let this = this - .upgrade(&cx) - .ok_or_else(|| anyhow!("project dropped"))?; - for reg in params.registrations { - if reg.method == "workspace/didChangeWatchedFiles" { - if let Some(options) = reg.register_options { - let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { - this.on_lsp_did_change_watched_files( - server_id, options, cx, - ); - }); - } - } - } - Ok(()) - }, - ) - .detach(); + language_server + .on_request::({ + let adapter = adapter.clone(); + move |params, cx| { + Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) + } + }) + .detach(); - language_server - .on_request::({ - let adapter = adapter.clone(); - move |params, cx| { - Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) - } - }) - .detach(); + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token.clone(); + language_server + .on_notification::(move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.on_lsp_progress( + params, + server_id, + disk_based_diagnostics_progress_token.clone(), + cx, + ); + }); + } + }) + .detach(); - language_server - .on_notification::({ - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.on_lsp_progress( - params, - server_id, - disk_based_diagnostics_progress_token.clone(), - cx, - ); - }); - } - } - }) - .detach(); + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); - let language_server = language_server - .initialize(initialization_options) - .await - .log_err()?; - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, - ) - .ok(); + Ok(language_server) + } - let this = this.upgrade(&cx)?; - this.update(&mut cx, |this, cx| { - // If the language server for this key doesn't match the server id, don't store the - // server. Which will cause it to be dropped, killing the process - if this - .language_server_ids - .get(&key) - .map(|id| id != &server_id) - .unwrap_or(false) - { - return None; - } + fn insert_newly_running_language_server( + &mut self, + language: Arc, + adapter: Arc, + language_server: Arc, + server_id: LanguageServerId, + key: (WorktreeId, LanguageServerName), + cx: &mut ModelContext, + ) -> Result<()> { + // If the language server for this key doesn't match the server id, don't store the + // server. Which will cause it to be dropped, killing the process + if self + .language_server_ids + .get(&key) + .map(|id| id != &server_id) + .unwrap_or(false) + { + return Ok(()); + } - // Update language_servers collection with Running variant of LanguageServerState - // indicating that the server is up and running and ready - this.language_servers.insert( - server_id, - LanguageServerState::Running { - adapter: adapter.clone(), - language: language.clone(), - watched_paths: Default::default(), - server: language_server.clone(), - simulate_disk_based_diagnostics_completion: None, - }, - ); - this.language_server_statuses.insert( - server_id, - LanguageServerStatus { - name: language_server.name().to_string(), - pending_work: Default::default(), - has_pending_diagnostic_updates: false, - progress_tokens: Default::default(), - }, - ); + // Update language_servers collection with Running variant of LanguageServerState + // indicating that the server is up and running and ready + self.language_servers.insert( + server_id, + LanguageServerState::Running { + adapter: adapter.clone(), + language: language.clone(), + watched_paths: Default::default(), + server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, + }, + ); - cx.emit(Event::LanguageServerAdded(server_id)); + self.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + has_pending_diagnostic_updates: false, + progress_tokens: Default::default(), + }, + ); - if let Some(project_id) = this.remote_id() { - this.client - .send(proto::StartLanguageServer { - project_id, - server: Some(proto::LanguageServer { - id: server_id.0 as u64, - name: language_server.name().to_string(), - }), - }) - .log_err(); + cx.emit(Event::LanguageServerAdded(server_id)); + + if let Some(project_id) = self.remote_id() { + self.client.send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id.0 as u64, + name: language_server.name().to_string(), + }), + })?; + } + + // Tell the language server about every open buffer in the worktree that matches the language. + for buffer in self.opened_buffers.values() { + if let Some(buffer_handle) = buffer.upgrade(cx) { + let buffer = buffer_handle.read(cx); + let file = match File::from_dyn(buffer.file()) { + Some(file) => file, + None => continue, + }; + let language = match buffer.language() { + Some(language) => language, + None => continue, + }; + + if file.worktree.read(cx).id() != key.0 + || !language.lsp_adapters().iter().any(|a| a.name == key.1) + { + continue; } - // Tell the language server about every open buffer in the worktree that matches the language. - for buffer in this.opened_buffers.values() { - if let Some(buffer_handle) = buffer.upgrade(cx) { - let buffer = buffer_handle.read(cx); - let file = match File::from_dyn(buffer.file()) { - Some(file) => file, - None => continue, - }; - let language = match buffer.language() { - Some(language) => language, - None => continue, - }; + let file = match file.as_local() { + Some(file) => file, + None => continue, + }; - if file.worktree.read(cx).id() != key.0 - || !language.lsp_adapters().iter().any(|a| a.name == key.1) - { - continue; - } + let versions = self + .buffer_snapshots + .entry(buffer.remote_id()) + .or_default() + .entry(server_id) + .or_insert_with(|| { + vec![LspBufferSnapshot { + version: 0, + snapshot: buffer.text_snapshot(), + }] + }); - let file = file.as_local()?; - let versions = this - .buffer_snapshots - .entry(buffer.remote_id()) - .or_default() - .entry(server_id) - .or_insert_with(|| { - vec![LspBufferSnapshot { - version: 0, - snapshot: buffer.text_snapshot(), - }] - }); + let snapshot = versions.last().unwrap(); + let version = snapshot.version; + let initial_snapshot = &snapshot.snapshot; + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + language_server.notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + adapter + .language_ids + .get(language.name().as_ref()) + .cloned() + .unwrap_or_default(), + version, + initial_snapshot.text(), + ), + }, + )?; - let snapshot = versions.last().unwrap(); - let version = snapshot.version; - let initial_snapshot = &snapshot.snapshot; - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + buffer_handle.update(cx, |buffer, cx| { + buffer.set_completion_triggers( language_server - .notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - adapter - .language_ids - .get(language.name().as_ref()) - .cloned() - .unwrap_or_default(), - version, - initial_snapshot.text(), - ), - }, - ) - .log_err()?; - buffer_handle.update(cx, |buffer, cx| { - buffer.set_completion_triggers( - language_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|provider| provider.trigger_characters.clone()) - .unwrap_or_default(), - cx, - ) - }); - } - } + .capabilities() + .completion_provider + .as_ref() + .and_then(|provider| provider.trigger_characters.clone()) + .unwrap_or_default(), + cx, + ) + }); + } + } - cx.notify(); - Some(language_server) - }) - })) + cx.notify(); + Ok(()) } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -2776,6 +2834,7 @@ impl Project { Some(LanguageServerState::Starting(started_language_server)) => { started_language_server.await } + Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; From bca625a197b52597011d012c7846a65f6052cb51 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 12 Jun 2023 14:25:45 -0400 Subject: [PATCH 002/169] Many steps toward validating and reinstalling server after failure --- .../src/activity_indicator.rs | 17 +- crates/copilot/src/copilot.rs | 15 +- crates/language/src/language.rs | 99 ++++++- crates/lsp/src/lsp.rs | 51 +++- crates/project/src/project.rs | 275 +++++++++++------- crates/util/src/util.rs | 14 +- crates/zed/src/languages/c.rs | 1 + crates/zed/src/languages/elixir.rs | 2 +- crates/zed/src/languages/go.rs | 1 + crates/zed/src/languages/html.rs | 3 +- crates/zed/src/languages/json.rs | 3 +- crates/zed/src/languages/language_plugin.rs | 3 +- crates/zed/src/languages/lua.rs | 3 +- crates/zed/src/languages/python.rs | 3 +- crates/zed/src/languages/ruby.rs | 3 +- crates/zed/src/languages/rust.rs | 1 + crates/zed/src/languages/typescript.rs | 4 +- crates/zed/src/languages/yaml.rs | 5 +- styles/tsconfig.json | 1 + 19 files changed, 348 insertions(+), 156 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 801c8f7172ec40d5c111109482d55e345d037592..a8874e8d9e89f42f03f3f47d493a773dd4619f7b 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -203,20 +203,17 @@ impl ActivityIndicator { } // Show any language server installation info. + let mut validating = SmallVec::<[_; 3]>::new(); let mut downloading = SmallVec::<[_; 3]>::new(); let mut checking_for_update = SmallVec::<[_; 3]>::new(); let mut failed = SmallVec::<[_; 3]>::new(); for status in &self.statuses { + let name = status.name.clone(); match status.status { - LanguageServerBinaryStatus::CheckingForUpdate => { - checking_for_update.push(status.name.clone()); - } - LanguageServerBinaryStatus::Downloading => { - downloading.push(status.name.clone()); - } - LanguageServerBinaryStatus::Failed { .. } => { - failed.push(status.name.clone()); - } + LanguageServerBinaryStatus::Validating => validating.push(name), + LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name), + LanguageServerBinaryStatus::Downloading => downloading.push(name), + LanguageServerBinaryStatus::Failed { .. } => failed.push(name), LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {} } } @@ -245,7 +242,7 @@ impl ActivityIndicator { ), on_click: None, }; - } else if !failed.is_empty() { + } else if !failed.is_empty() || !validating.is_empty() { return Content { icon: Some(WARNING_ICON), message: format!( diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index e73424f0cd36945f1a24d3155521075c7967fa80..b72f00f36139484ef4e52d0a77cf88897ddfbc64 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -15,7 +15,7 @@ use language::{ ToPointUtf16, }; use log::{debug, error}; -use lsp::{LanguageServer, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinaries, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; use settings::SettingsStore; @@ -361,11 +361,18 @@ impl Copilot { let start_language_server = async { let server_path = get_copilot_lsp(http).await?; let node_path = node_runtime.binary_path().await?; - let arguments: &[OsString] = &[server_path.into(), "--stdio".into()]; + let arguments: Vec = vec![server_path.into(), "--stdio".into()]; + let binary = LanguageServerBinary { + path: node_path, + arguments, + }; + let binaries = LanguageServerBinaries { + binary: binary.clone(), + installation_test_binary: binary, + }; let server = LanguageServer::new( LanguageServerId(0), - &node_path, - arguments, + binaries, Path::new("/"), None, cx.clone(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fa85d0e21f64561d465511f27084f3899f6f6f47..2ed896e03fbde49aae53adc8b9426d90765ce65a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::CodeActionKind; +use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -30,17 +30,18 @@ use std::{ any::Any, borrow::Cow, cell::RefCell, - ffi::OsString, fmt::Debug, hash::Hash, mem, ops::{Not, Range}, path::{Path, PathBuf}, + process::Stdio, str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, + time::Duration, }; use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; @@ -86,12 +87,6 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); -#[derive(Debug, Clone, Deserialize)] -pub struct LanguageServerBinary { - pub path: PathBuf, - pub arguments: Vec, -} - /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -148,6 +143,10 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } + async fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { + self.adapter.installation_test_binary(container_dir) + } + pub fn code_action_kinds(&self) -> Option> { self.adapter.code_action_kinds() } @@ -205,6 +204,10 @@ pub trait LspAdapter: 'static + Send + Sync { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; + fn installation_test_binary(&self, _container_dir: PathBuf) -> LanguageServerBinary { + unimplemented!(); + } + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn process_completion(&self, _: &mut lsp::CompletionItem) {} @@ -485,6 +488,7 @@ struct BracketConfig { #[derive(Clone)] pub enum LanguageServerBinaryStatus { + Validating, CheckingForUpdate, Downloading, Downloaded, @@ -515,7 +519,7 @@ pub struct LanguageRegistry { lsp_binary_paths: Mutex< HashMap< LanguageServerName, - Shared>>>, + Shared>>>, >, >, executor: Option>, @@ -891,8 +895,7 @@ impl LanguageRegistry { let binary = entry.clone().map_err(|e| anyhow!(e)).await?; let server = lsp::LanguageServer::new( server_id, - &binary.path, - &binary.arguments, + binary, &root_path, adapter.code_action_kinds(), cx, @@ -909,6 +912,56 @@ impl LanguageRegistry { ) -> async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)> { self.lsp_binary_statuses_rx.clone() } + + pub async fn check_errored_lsp_installation( + &self, + language_server: Arc, + cx: &mut AppContext, + ) { + // Check if child process is running + if !language_server.is_dead() { + return; + } + + // If not, get check binary + let test_binary = match language_server.test_installation_binary() { + Some(test_binary) => test_binary.clone(), + None => return, + }; + + // Run + const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); + let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); + + let mut errored = false; + let result = smol::process::Command::new(&test_binary.path) + .current_dir(&test_binary.path) + .args(test_binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn(); + + if let Ok(mut process) = result { + futures::select! { + _ = process.status().fuse() => {} + _ = timeout => errored = true, + } + } else { + errored = true; + } + + dbg!(errored); + + // If failure clear container dir + + // Prompt binary retrieval + + // Start language server + + // Update project server state + } } impl LanguageRegistryState { @@ -961,7 +1014,7 @@ async fn get_binary( http_client: Arc, download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) @@ -979,11 +1032,15 @@ async fn get_binary( .await; if let Err(error) = binary.as_ref() { - if let Some(cached) = adapter.cached_server_binary(container_dir).await { + if let Some(binary) = adapter.cached_server_binary(container_dir.clone()).await { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - return Ok(cached); + let installation_test_binary = adapter.installation_test_binary(container_dir).await; + return Ok(LanguageServerBinaries { + binary, + installation_test_binary, + }); } else { statuses .broadcast(( @@ -995,6 +1052,7 @@ async fn get_binary( .await?; } } + binary } @@ -1004,7 +1062,7 @@ async fn fetch_latest_binary( http_client: Arc, container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( @@ -1012,19 +1070,28 @@ async fn fetch_latest_binary( LanguageServerBinaryStatus::CheckingForUpdate, )) .await?; + let version_info = adapter .fetch_latest_server_version(http_client.clone()) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; + let binary = adapter .fetch_server_binary(version_info, http_client, container_dir.to_path_buf()) .await?; + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; - Ok(binary) + + Ok(LanguageServerBinaries { + binary, + installation_test_binary, + }) } impl Language { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index f05acbb1a2d57d011a82e7b058db8965d1f52045..94a5096c5f08d7878c4480c17ecea698c34a696e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -16,6 +16,7 @@ use smol::{ process::{self, Child}, }; use std::{ + ffi::OsString, fmt, future::Future, io::Write, @@ -36,6 +37,18 @@ type NotificationHandler = Box, &str, AsyncAppCon type ResponseHandler = Box)>; type IoHandler = Box; +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinary { + pub path: PathBuf, + pub arguments: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinaries { + pub binary: LanguageServerBinary, + pub installation_test_binary: LanguageServerBinary, +} + pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -51,7 +64,8 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, - _server: Option, + server: Option>, + test_installation_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -119,10 +133,9 @@ struct Error { } impl LanguageServer { - pub fn new>( + pub fn new( server_id: LanguageServerId, - binary_path: &Path, - arguments: &[T], + binaries: LanguageServerBinaries, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -133,9 +146,9 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(binary_path) + let mut server = process::Command::new(&binaries.binary.path) .current_dir(working_dir) - .args(arguments) + .args(binaries.binary.arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -149,6 +162,7 @@ impl LanguageServer { stdin, stout, Some(server), + Some(binaries.installation_test_binary), root_path, code_action_kinds, cx, @@ -164,7 +178,7 @@ impl LanguageServer { }, ); - if let Some(name) = binary_path.file_name() { + if let Some(name) = binaries.binary.path.file_name() { server.name = name.to_string_lossy().to_string(); } @@ -176,6 +190,7 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, + test_installation_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -229,10 +244,28 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - _server: server, + server: server.map(|server| Mutex::new(server)), + test_installation_binary, } } + pub fn is_dead(&self) -> bool { + let server = match self.server.as_ref() { + Some(server) => server, + None => return false, // Fake server for tests + }; + + match server.lock().try_status() { + Ok(Some(_)) => true, + Ok(None) => false, + Err(_) => true, + } + } + + pub fn test_installation_binary(&self) -> &Option { + &self.test_installation_binary + } + pub fn code_action_kinds(&self) -> Option> { self.code_action_kinds.clone() } @@ -813,6 +846,7 @@ impl LanguageServer { stdin_writer, stdout_reader, None, + None, Path::new("/"), None, cx.clone(), @@ -824,6 +858,7 @@ impl LanguageServer { stdout_writer, stdin_reader, None, + None, Path::new("/"), None, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f78ff19eaef366b76f8ea5d1281560057f27bbd6..5c9b79434aea4d1da0dd901a06ef44511460cdc1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -45,7 +45,7 @@ use language::{ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, - DocumentHighlightKind, LanguageServer, LanguageServerId, + DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, }; use lsp_command::*; use postage::watch; @@ -65,6 +65,7 @@ use std::{ num::NonZeroU32, ops::Range, path::{Component, Path, PathBuf}, + process::Stdio, rc::Rc, str, sync::{ @@ -277,6 +278,7 @@ pub enum Event { } pub enum LanguageServerState { + Validating(Task>>), Starting(Task>>), Running { language: Arc, @@ -2447,7 +2449,7 @@ impl Project { Err(err) => { log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check + // TODO: Prompt installation validity check LSP ERROR None } } @@ -2831,10 +2833,8 @@ impl Project { let mut root_path = None; let server = match server_state { - Some(LanguageServerState::Starting(started_language_server)) => { - started_language_server.await - } - + Some(LanguageServerState::Validating(task)) => task.await, + Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -2945,6 +2945,15 @@ impl Project { .detach(); } + fn check_errored_lsp_installation( + &self, + language_server: Arc, + cx: &mut ModelContext, + ) { + self.languages + .check_errored_lsp_installation(language_server, cx); + } + fn on_lsp_progress( &mut self, progress: lsp::ProgressParams, @@ -3716,29 +3725,26 @@ impl Project { tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { - let text_document = - lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap()); + let uri = lsp::Url::from_file_path(abs_path) + .map_err(|_| anyhow!("failed to convert abs path to uri"))?; + let text_document = lsp::TextDocumentIdentifier::new(uri); let capabilities = &language_server.capabilities(); - let lsp_edits = if capabilities - .document_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + + let formatting_provider = capabilities.document_formatting_provider.as_ref(); + let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); + + let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server .request::(lsp::DocumentFormattingParams { text_document, options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await? - } else if capabilities - .document_range_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + .await + } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); - let buffer_end = - buffer.read_with(cx, |buffer, _| point_to_lsp(buffer.max_point_utf16())); + let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); + language_server .request::(lsp::DocumentRangeFormattingParams { text_document, @@ -3746,9 +3752,27 @@ impl Project { options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await? + .await } else { - None + Ok(None) + }; + + let lsp_edits = match result { + Ok(lsp_edits) => lsp_edits, + + Err(err) => { + log::warn!( + "Error firing format request to {}: {}", + language_server.name(), + err + ); + + this.update(cx, |this, cx| { + this.check_errored_lsp_installation(language_server.clone(), cx); + }); + + None + } }; if let Some(lsp_edits) = lsp_edits { @@ -3757,7 +3781,7 @@ impl Project { }) .await } else { - Ok(Default::default()) + Ok(Vec::new()) } } @@ -3865,80 +3889,89 @@ impl Project { let mut requests = Vec::new(); for ((worktree_id, _), server_id) in self.language_server_ids.iter() { let worktree_id = *worktree_id; - if let Some(worktree) = self - .worktree_for_id(worktree_id, cx) - .and_then(|worktree| worktree.read(cx).as_local()) - { - if let Some(LanguageServerState::Running { + let worktree_handle = self.worktree_for_id(worktree_id, cx); + let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) { + Some(worktree) => worktree, + None => continue, + }; + let worktree_abs_path = worktree.abs_path().clone(); + + let (adapter, language, server) = match self.language_servers.get(server_id) { + Some(LanguageServerState::Running { adapter, language, server, .. - }) = self.language_servers.get(server_id) - { - let adapter = adapter.clone(); - let language = language.clone(); - let worktree_abs_path = worktree.abs_path().clone(); - requests.push( - server - .request::( - lsp::WorkspaceSymbolParams { - query: query.to_string(), - ..Default::default() - }, - ) - .log_err() - .map(move |response| { - let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { - lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { - flat_responses.into_iter().map(|lsp_symbol| { - (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) - }).collect::>() - } - lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { - nested_responses.into_iter().filter_map(|lsp_symbol| { - let location = match lsp_symbol.location { - lsp::OneOf::Left(location) => location, - lsp::OneOf::Right(_) => { - error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); - return None - } - }; - Some((lsp_symbol.name, lsp_symbol.kind, location)) - }).collect::>() - } - }).unwrap_or_default(); - - ( - adapter, - language, - worktree_id, - worktree_abs_path, - lsp_symbols, - ) - }), - ); - } - } + }) => (adapter.clone(), language.clone(), server), + + _ => continue, + }; + + requests.push( + server + .request::( + lsp::WorkspaceSymbolParams { + query: query.to_string(), + ..Default::default() + }, + ) + .map_ok(move |response| { + let lsp_symbols = response.map(|symbol_response| match symbol_response { + lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { + flat_responses.into_iter().map(|lsp_symbol| { + (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) + }).collect::>() + } + lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { + nested_responses.into_iter().filter_map(|lsp_symbol| { + let location = match lsp_symbol.location { + OneOf::Left(location) => location, + OneOf::Right(_) => { + error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); + return None + } + }; + Some((lsp_symbol.name, lsp_symbol.kind, location)) + }).collect::>() + } + }).unwrap_or_default(); + + ( + adapter, + language, + worktree_id, + worktree_abs_path, + lsp_symbols, + ) + }), + ); } cx.spawn_weak(|this, cx| async move { let responses = futures::future::join_all(requests).await; - let this = if let Some(this) = this.upgrade(&cx) { - this - } else { - return Ok(Default::default()); + let this = match this.upgrade(&cx) { + Some(this) => this, + None => return Ok(Vec::new()), }; + let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); - for ( - adapter, - adapter_language, - source_worktree_id, - worktree_abs_path, - lsp_symbols, - ) in responses - { + for response in responses { + let ( + adapter, + adapter_language, + source_worktree_id, + worktree_abs_path, + lsp_symbols, + ) = match response { + Ok(response) => response, + + Err(err) => { + // TODO: Prompt installation validity check LSP ERROR + return Vec::new(); + } + }; + symbols.extend(lsp_symbols.into_iter().filter_map( |(symbol_name, symbol_kind, symbol_location)| { let abs_path = symbol_location.uri.to_file_path().ok()?; @@ -3985,8 +4018,10 @@ impl Project { }, )); } + symbols }); + Ok(futures::future::join_all(symbols).await) }) } else if let Some(project_id) = self.remote_id() { @@ -4111,9 +4146,17 @@ impl Project { }; cx.spawn(|this, mut cx| async move { - let resolved_completion = lang_server + let resolved_completion = match lang_server .request::(completion.lsp_completion) - .await?; + .await + { + Ok(resolved_completion) => resolved_completion, + + Err(err) => { + // TODO: LSP ERROR + return Ok(None); + } + }; if let Some(edits) = resolved_completion.additional_text_edits { let edits = this @@ -4232,9 +4275,17 @@ impl Project { .and_then(|d| d.get_mut("range")) { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); - action.lsp_action = lang_server + action.lsp_action = match lang_server .request::(action.lsp_action) - .await?; + .await + { + Ok(lsp_action) => lsp_action, + + Err(err) => { + // LSP ERROR + return Err(err); + } + }; } else { let actions = this .update(&mut cx, |this, cx| { @@ -4267,13 +4318,20 @@ impl Project { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()); }); - lang_server + + let result = lang_server .request::(lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), ..Default::default() }) - .await?; + .await; + + if let Err(err) = result { + // TODO: LSP ERROR + return Err(err); + } + return Ok(this.update(&mut cx, |this, _| { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()) @@ -4433,7 +4491,7 @@ impl Project { uri, version: None, }, - edits: edits.into_iter().map(lsp::OneOf::Left).collect(), + edits: edits.into_iter().map(OneOf::Left).collect(), }) })); } @@ -4503,8 +4561,8 @@ impl Project { let edits = this .update(cx, |this, cx| { let edits = op.edits.into_iter().map(|edit| match edit { - lsp::OneOf::Left(edit) => edit, - lsp::OneOf::Right(edit) => edit.text_edit, + OneOf::Left(edit) => edit, + OneOf::Right(edit) => edit.text_edit, }); this.edits_from_lsp( &buffer_to_edit, @@ -4841,10 +4899,20 @@ impl Project { return Ok(Default::default()); } - let response = language_server - .request::(lsp_params) - .await - .context("lsp request failed")?; + let result = language_server.request::(lsp_params).await; + let response = match result { + Ok(response) => response, + + Err(err) => { + log::warn!( + "Generic lsp request to {} failed: {}", + language_server.name(), + err + ); + return Err(err); + } + }; + request .response_from_lsp( response, @@ -7211,11 +7279,10 @@ impl Entity for Project { .language_servers .drain() .map(|(_, server_state)| async { + use LanguageServerState::*; match server_state { - LanguageServerState::Running { server, .. } => server.shutdown()?.await, - LanguageServerState::Starting(starting_server) => { - starting_server.await?.shutdown()?.await - } + Running { server, .. } => server.shutdown()?.await, + Starting(task) | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 71ffacebf34e2474bcb1c394dada124827c53477..c8beb86aeff44a75482531d22a6e8b0c07155fa6 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -118,14 +118,15 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se } } -pub trait ResultExt { +pub trait ResultExt { type Ok; fn log_err(self) -> Option; fn warn_on_err(self) -> Option; + fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } -impl ResultExt for Result +impl ResultExt for Result where E: std::fmt::Debug, { @@ -152,6 +153,15 @@ where } } } + + /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err + fn inspect_error(self, func: impl FnOnce(&E)) -> Self { + if let Err(err) = &self { + func(err); + } + + self + } } pub trait TryFutureExt { diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 7e4ddcef19189cb0a5704d43ca78a6164186d1de..bfb25f55a091f90b41059d9fa0805444239f5ba3 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; +use lsp::LanguageServerBinary; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::fs::remove_matching; diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 2939a0fa5f942b9631b2ecd9e13d6c1e5c4de17e..8d6ece28c75c6cbe9dac34592c7002e917f8ebdc 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; -use lsp::{CompletionItemKind, SymbolKind}; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::fs::remove_matching; diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index ed24abb45c40b6b2cc2c2336a746557c03505745..9ffd88e53a996303e45e4c623bde3066cf3a1bfa 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use futures::StreamExt; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::{fs, process}; use std::ffi::{OsStr, OsString}; diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 68f780c3af73b3febd1fdcbeaba1a2a95bb36b37..3d5adb88ba657080f88d70fde3bfddcfacbac23e 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index e1f3da9e0238f0a91179447e7c15ce2df5239ec1..f6ca3f403fd8083fd3b30ad5d1547bef5296b625 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageRegistry, LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9b82713d082d5d12a0243ebaf674b9a16550c3d4..4ff4816ae3eb007e6994ca88214939004f2a5972 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::http::HttpClient; diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index f204eb2555f5327d0e70df60963db771acd772ab..bb9070c1a2501260857d75b5e0845ea8283dac10 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -3,7 +3,8 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerBinary, LanguageServerName}; +use language::LanguageServerName; +use lsp::LanguageServerBinary; use smol::fs; use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; use util::{async_iife, github::latest_github_release, http::HttpClient, ResultExt}; diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 7aaddf5fe8a72d5731322e911b5a5c87473dfdbe..6dd17660c4ec420d4cc31d17bc32073f23019ea7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use smol::fs; use std::{ diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index d387f815f0cd345c4173f522370ab17495989592..189641bec5f1673f2575a7fb19c1f973b7a91656 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use std::{any::Any, path::PathBuf, sync::Arc}; use util::http::HttpClient; diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 15700ec80a1bc798d44210ed47d600cc319a1241..67a38d1355d0656e1b146c37c80b59eb59d8bce2 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::fs::{self, File}; use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 7d2d580857780a714496a5f8c2c850de36986d26..dd2ac2234881fbfe8e47bd52d6b52cf3142da959 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,8 +4,8 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; -use lsp::CodeActionKind; +use language::{LanguageServerName, LspAdapter}; +use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; use smol::{fs, io::BufReader, stream::StreamExt}; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 7f87a7caedb764588a698b5e863fbd418c3859ed..48ce76721d7ff778acc47fd198746376b362eb17 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -2,9 +2,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{ - language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, -}; +use language::{language_settings::all_language_settings, LanguageServerName, LspAdapter}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::Value; use smol::fs; diff --git a/styles/tsconfig.json b/styles/tsconfig.json index 6d24728a0a9f0e38b3a038c7c7bf3d0642e6e88f..fe9682b9697ceaf53023527e668eb836a80a5c44 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -1,4 +1,5 @@ { + // "noErrorTruncation": true, "compilerOptions": { "target": "es2015", "module": "commonjs", From 4d24eae901fd2e94efb02778207c25bbbc4ce749 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 14 Jun 2023 12:27:08 -0400 Subject: [PATCH 003/169] Actually check and reinstall broken server --- crates/language/src/language.rs | 66 ++++++---------------- crates/project/src/project.rs | 98 ++++++++++++++++++++++++++++++++- crates/zed/src/languages/c.rs | 4 ++ 3 files changed, 118 insertions(+), 50 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2ed896e03fbde49aae53adc8b9426d90765ce65a..28cd1cd5b1ccd5b007fffe18d197ce7191be0a20 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::{CodeActionKind, LanguageServer, LanguageServerBinaries, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinaries, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -35,13 +35,11 @@ use std::{ mem, ops::{Not, Range}, path::{Path, PathBuf}, - process::Stdio, str, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::Duration, }; use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; @@ -860,7 +858,7 @@ impl LanguageRegistry { let download_dir = self .language_server_download_dir .clone() - .ok_or_else(|| anyhow!("language server download directory has not been assigned")) + .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server")) .log_err()?; let this = self.clone(); let language = language.clone(); @@ -913,54 +911,26 @@ impl LanguageRegistry { self.lsp_binary_statuses_rx.clone() } - pub async fn check_errored_lsp_installation( + pub fn delete_server_container( &self, - language_server: Arc, + adapter: Arc, cx: &mut AppContext, - ) { - // Check if child process is running - if !language_server.is_dead() { - return; - } - - // If not, get check binary - let test_binary = match language_server.test_installation_binary() { - Some(test_binary) => test_binary.clone(), - None => return, - }; - - // Run - const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); - let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); - - let mut errored = false; - let result = smol::process::Command::new(&test_binary.path) - .current_dir(&test_binary.path) - .args(test_binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn(); - - if let Ok(mut process) = result { - futures::select! { - _ = process.status().fuse() => {} - _ = timeout => errored = true, - } - } else { - errored = true; - } - - dbg!(errored); + ) -> Task<()> { + let mut lock = self.lsp_binary_paths.lock(); + lock.remove(&adapter.name); - // If failure clear container dir - - // Prompt binary retrieval + let download_dir = self + .language_server_download_dir + .clone() + .expect("language server download directory has not been assigned before deleting server container"); - // Start language server - - // Update project server state + cx.spawn(|_| async move { + let container_dir = download_dir.join(adapter.name.0.as_ref()); + smol::fs::remove_dir_all(container_dir) + .await + .context("server container removal") + .log_err(); + }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c9b79434aea4d1da0dd901a06ef44511460cdc1..e131bbb746da6d953669fe5daf75417d84df5b29 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2461,6 +2461,54 @@ impl Project { } } + fn reinstall_language_server( + &mut self, + server_id: LanguageServerId, + cx: &mut ModelContext, + ) -> Option> { + let (adapter, language, server) = match self.language_servers.remove(&server_id) { + Some(LanguageServerState::Running { + adapter, + language, + server, + .. + }) => (adapter.clone(), language.clone(), server), + + _ => return None, + }; + + Some(cx.spawn(move |this, mut cx| async move { + if let Some(task) = server.shutdown() { + task.await; + } + + // TODO: This is race-safe with regards to preventing new instances from + // starting while deleting, but existing instances in other projects are going + // to be very confused and messed up + this.update(&mut cx, |this, cx| { + this.languages.delete_server_container(adapter.clone(), cx) + }) + .await; + + this.update(&mut cx, |this, mut cx| { + for worktree in &this.worktrees { + let root_path = match worktree.upgrade(cx) { + Some(worktree) => worktree.read(cx).abs_path(), + None => continue, + }; + + this.languages.start_language_server( + language.clone(), + adapter.clone(), + root_path, + this.client.http_client(), + &mut cx, + ); + } + }) + })) + } + async fn setup_and_insert_language_server( this: WeakModelHandle, initialization_options: Option, @@ -2950,8 +2998,54 @@ impl Project { language_server: Arc, cx: &mut ModelContext, ) { - self.languages - .check_errored_lsp_installation(language_server, cx); + cx.spawn(|this, mut cx| async move { + if !language_server.is_dead() { + return; + } + + let server_id = language_server.server_id(); + let test_binary = match language_server.test_installation_binary() { + Some(test_binary) => test_binary.clone(), + None => return, + }; + + const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); + let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); + + let mut errored = false; + let result = smol::process::Command::new(&test_binary.path) + .current_dir(&test_binary.path) + .args(test_binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn(); + + if let Ok(mut process) = result { + futures::select! { + status = process.status().fuse() => match status { + Ok(status) => errored = !status.success(), + Err(_) => errored = true, + }, + + _ = timeout => {} + } + } else { + errored = true; + } + + if errored { + let task = this.update(&mut cx, move |this, mut cx| { + this.reinstall_language_server(server_id, &mut cx) + }); + + if let Some(task) = task { + task.await; + } + } + }) + .detach(); } fn on_lsp_progress( diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index bfb25f55a091f90b41059d9fa0805444239f5ba3..9d4e1802d5bd5648ca62fe03ff1c61ba8ba01a00 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -109,6 +109,10 @@ impl super::LspAdapter for CLspAdapter { .await .log_err() } + + fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { + unimplemented!(); + } async fn label_for_completion( &self, From f81ccbd652642bf6852014b86bf138813a005d92 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 15 Jun 2023 12:03:11 -0400 Subject: [PATCH 004/169] Setup C adapter with test binary --- crates/copilot/src/copilot.rs | 2 +- crates/language/src/language.rs | 12 +++++++++--- crates/lsp/src/lsp.rs | 4 ++-- crates/project/src/project.rs | 32 +++++++++++++++++--------------- crates/zed/src/languages/c.rs | 14 +++++++++++--- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index b72f00f36139484ef4e52d0a77cf88897ddfbc64..866bb8d7f526358a7cacb833c84037c87a23b878 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -368,7 +368,7 @@ impl Copilot { }; let binaries = LanguageServerBinaries { binary: binary.clone(), - installation_test_binary: binary, + installation_test_binary: Some(binary), }; let server = LanguageServer::new( LanguageServerId(0), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 28cd1cd5b1ccd5b007fffe18d197ce7191be0a20..c25027f9bc0feef3ef75ffac8d5cbbcbe10e8306 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -141,8 +141,11 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } - async fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { - self.adapter.installation_test_binary(container_dir) + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + self.adapter.installation_test_binary(container_dir).await } pub fn code_action_kinds(&self) -> Option> { @@ -202,7 +205,10 @@ pub trait LspAdapter: 'static + Send + Sync { async fn cached_server_binary(&self, container_dir: PathBuf) -> Option; - fn installation_test_binary(&self, _container_dir: PathBuf) -> LanguageServerBinary { + async fn installation_test_binary( + &self, + _container_dir: PathBuf, + ) -> Option { unimplemented!(); } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 94a5096c5f08d7878c4480c17ecea698c34a696e..f7d924604b69402a27210fb1df71a1da932d164a 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -46,7 +46,7 @@ pub struct LanguageServerBinary { #[derive(Debug, Clone, Deserialize)] pub struct LanguageServerBinaries { pub binary: LanguageServerBinary, - pub installation_test_binary: LanguageServerBinary, + pub installation_test_binary: Option, } pub struct LanguageServer { @@ -162,7 +162,7 @@ impl LanguageServer { stdin, stout, Some(server), - Some(binaries.installation_test_binary), + binaries.installation_test_binary, root_path, code_action_kinds, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e131bbb746da6d953669fe5daf75417d84df5b29..9641ecc0f13f01727eb969808cd22ab3526e0aa0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3002,27 +3002,29 @@ impl Project { if !language_server.is_dead() { return; } - let server_id = language_server.server_id(); - let test_binary = match language_server.test_installation_binary() { - Some(test_binary) => test_binary.clone(), - None => return, - }; + + // A lack of test binary counts as a failure + let process = language_server + .test_installation_binary() + .as_ref() + .and_then(|binary| { + smol::process::Command::new(&binary.path) + .current_dir(&binary.path) + .args(&binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn() + .ok() + }); const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); let mut errored = false; - let result = smol::process::Command::new(&test_binary.path) - .current_dir(&test_binary.path) - .args(test_binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn(); - - if let Ok(mut process) = result { + if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { Ok(status) => errored = !status.success(), diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 9d4e1802d5bd5648ca62fe03ff1c61ba8ba01a00..0f580f1d4fa02d967c887825fc23e24c92c520c7 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -109,9 +109,17 @@ impl super::LspAdapter for CLspAdapter { .await .log_err() } - - fn installation_test_binary(&self, container_dir: PathBuf) -> LanguageServerBinary { - unimplemented!(); + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + self.cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) } async fn label_for_completion( From abe5ecc5ec01ac7216747632492c5cf39ef61299 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 15 Jun 2023 13:56:07 -0400 Subject: [PATCH 005/169] Actually fully start reinstalled language server --- crates/language/src/language.rs | 2 +- crates/project/src/project.rs | 145 ++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 64 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c25027f9bc0feef3ef75ffac8d5cbbcbe10e8306..1d595d0e2412cd060a3d83994bbde5aaea0ed703 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -815,7 +815,7 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_language_server( + pub fn start_pending_language_server( self: &Arc, language: Arc, adapter: Arc, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9641ecc0f13f01727eb969808cd22ab3526e0aa0..41a9d65069c34f8c39934487cea286dbd55e940c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -224,6 +224,7 @@ enum OpenBuffer { Operations(Vec), } +#[derive(Clone)] enum WorktreeHandle { Strong(ModelHandle), Weak(WeakModelHandle), @@ -2393,72 +2394,87 @@ impl Project { let worktree_id = worktree.read(cx).id(); for adapter in language.lsp_adapters() { - let key = (worktree_id, adapter.name.clone()); - if self.language_server_ids.contains_key(&key) { - continue; - } - - let pending_server = match self.languages.start_language_server( - language.clone(), - adapter.clone(), + self.start_language_server( + worktree_id, worktree_path.clone(), - self.client.http_client(), + adapter.clone(), + language.clone(), cx, - ) { - Some(pending_server) => pending_server, - None => continue, - }; + ); + } + } + + fn start_language_server( + &mut self, + worktree_id: WorktreeId, + worktree_path: Arc, + adapter: Arc, + language: Arc, + cx: &mut ModelContext, + ) { + let key = (worktree_id, adapter.name.clone()); + if self.language_server_ids.contains_key(&key) { + return; + } - let project_settings = settings::get::(cx); - let lsp = project_settings.lsp.get(&adapter.name.0); - let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); + let pending_server = match self.languages.start_pending_language_server( + language.clone(), + adapter.clone(), + worktree_path, + self.client.http_client(), + cx, + ) { + Some(pending_server) => pending_server, + None => return, + }; - 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 project_settings = settings::get::(cx); + 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 state = LanguageServerState::Starting({ - let server_name = adapter.name.0.clone(); - let adapter = adapter.clone(); - let language = language.clone(); - let languages = self.languages.clone(); - let key = key.clone(); - - cx.spawn_weak(|this, cx| async move { - let result = Self::setup_and_insert_language_server( - this, - initialization_options, - pending_server, - adapter, - languages, - language, - server_id, - key, - cx, - ) - .await; + let server_id = pending_server.server_id; + let state = LanguageServerState::Starting({ + let server_name = adapter.name.0.clone(); + let languages = self.languages.clone(); + let key = key.clone(); - match result { - Ok(server) => Some(server), + cx.spawn_weak(|this, cx| async move { + let result = Self::setup_and_insert_language_server( + this, + initialization_options, + pending_server, + adapter, + languages, + language, + server_id, + key, + cx, + ) + .await; - Err(err) => { - log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check LSP ERROR - None - } + match result { + Ok(server) => Some(server), + + Err(err) => { + log::warn!("Error starting language server {:?}: {}", server_name, err); + // TODO: Prompt installation validity check LSP ERROR + None } - }) - }); + } + }) + }); - self.language_servers.insert(server_id, state); - self.language_server_ids.insert(key, server_id); - } + self.language_servers.insert(server_id, state); + self.language_server_ids.insert(key, server_id); } fn reinstall_language_server( @@ -2491,17 +2507,20 @@ impl Project { .await; this.update(&mut cx, |this, mut cx| { - for worktree in &this.worktrees { - let root_path = match worktree.upgrade(cx) { - Some(worktree) => worktree.read(cx).abs_path(), + let worktrees = this.worktrees.clone(); + for worktree in worktrees { + let worktree = match worktree.upgrade(cx) { + Some(worktree) => worktree.read(cx), None => continue, }; + let worktree_id = worktree.id(); + let root_path = worktree.abs_path(); - this.languages.start_language_server( - language.clone(), - adapter.clone(), + this.start_language_server( + worktree_id, root_path, - this.client.http_client(), + adapter.clone(), + language.clone(), &mut cx, ); } From da2ee550133374ea75545b08f34d93527d551fa6 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 15:18:12 -0400 Subject: [PATCH 006/169] Route some more information for reinstall after startup failure Doesn't actually reinstall on that particular failure due to wrong variant in hashmap --- crates/language/src/language.rs | 101 +++++++++++++++++++------------- crates/lsp/src/lsp.rs | 10 ++-- crates/project/src/project.rs | 94 ++++++++++++++++++----------- 3 files changed, 126 insertions(+), 79 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1d595d0e2412cd060a3d83994bbde5aaea0ed703..e2c7b99763c2d783199bf1e0bc1f5824b2be7793 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -141,7 +141,7 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } - async fn installation_test_binary( + pub async fn installation_test_binary( &self, container_dir: PathBuf, ) -> Option { @@ -544,6 +544,7 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, pub task: Task>, + pub container_dir: Option>, } impl LanguageRegistry { @@ -824,8 +825,8 @@ impl LanguageRegistry { cx: &mut AppContext, ) -> Option { let server_id = self.state.write().next_language_server_id(); - log::info!( - "starting language server name:{}, path:{root_path:?}, id:{server_id}", + println!( + "starting language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -858,7 +859,11 @@ impl LanguageRegistry { Ok(server) }); - return Some(PendingLanguageServer { server_id, task }); + return Some(PendingLanguageServer { + server_id, + task, + container_dir: None, + }); } let download_dir = self @@ -869,46 +874,54 @@ impl LanguageRegistry { let this = self.clone(); let language = language.clone(); let http_client = http_client.clone(); - let download_dir = download_dir.clone(); + let container_dir: Arc = Arc::from(download_dir.join(adapter.name.0.as_ref())); let root_path = root_path.clone(); let adapter = adapter.clone(); let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); - let task = cx.spawn(|cx| async move { - login_shell_env_loaded.await; - - let mut lock = this.lsp_binary_paths.lock(); - let entry = lock - .entry(adapter.name.clone()) - .or_insert_with(|| { - get_binary( - adapter.clone(), - language.clone(), - http_client, - download_dir, - lsp_binary_statuses, - ) - .map_err(Arc::new) - .boxed() - .shared() - }) - .clone(); - drop(lock); + let task = { + let container_dir = container_dir.clone(); + cx.spawn(|cx| async move { + login_shell_env_loaded.await; - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; - let server = lsp::LanguageServer::new( - server_id, - binary, - &root_path, - adapter.code_action_kinds(), - cx, - )?; + let mut lock = this.lsp_binary_paths.lock(); + let entry = lock + .entry(adapter.name.clone()) + .or_insert_with(|| { + get_binaries( + adapter.clone(), + language.clone(), + http_client, + container_dir, + lsp_binary_statuses, + ) + .map_err(Arc::new) + .boxed() + .shared() + }) + .clone(); + drop(lock); + + let binaries = entry.clone().map_err(|e| anyhow!(e)).await?; + println!("starting server"); + let server = lsp::LanguageServer::new( + server_id, + binaries, + &root_path, + adapter.code_action_kinds(), + cx, + )?; - Ok(server) - }); + Ok(server) + }) + }; - Some(PendingLanguageServer { server_id, task }) + Some(PendingLanguageServer { + server_id, + task, + container_dir: Some(container_dir), + }) } pub fn language_server_binary_statuses( @@ -922,6 +935,7 @@ impl LanguageRegistry { adapter: Arc, cx: &mut AppContext, ) -> Task<()> { + println!("deleting server container"); let mut lock = self.lsp_binary_paths.lock(); lock.remove(&adapter.name); @@ -984,20 +998,20 @@ impl Default for LanguageRegistry { } } -async fn get_binary( +async fn get_binaries( adapter: Arc, language: Arc, http_client: Arc, - download_dir: Arc, + container_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await .context("failed to create container directory")?; } + println!("fetching binary"); let binary = fetch_latest_binary( adapter.clone(), language.clone(), @@ -1008,11 +1022,16 @@ async fn get_binary( .await; if let Err(error) = binary.as_ref() { - if let Some(binary) = adapter.cached_server_binary(container_dir.clone()).await { + if let Some(binary) = adapter + .cached_server_binary(container_dir.to_path_buf()) + .await + { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - let installation_test_binary = adapter.installation_test_binary(container_dir).await; + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; return Ok(LanguageServerBinaries { binary, installation_test_binary, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index f7d924604b69402a27210fb1df71a1da932d164a..cd7c1a0355cdc92de421fe66434fc7e32821908f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -65,7 +65,7 @@ pub struct LanguageServer { output_done_rx: Mutex>, root_path: PathBuf, server: Option>, - test_installation_binary: Option, + installation_test_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -190,7 +190,7 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, - test_installation_binary: Option, + installation_test_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -245,7 +245,7 @@ impl LanguageServer { output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), server: server.map(|server| Mutex::new(server)), - test_installation_binary, + installation_test_binary, } } @@ -262,8 +262,8 @@ impl LanguageServer { } } - pub fn test_installation_binary(&self) -> &Option { - &self.test_installation_binary + pub fn installation_test_binary(&self) -> &Option { + &self.installation_test_binary } pub fn code_action_kinds(&self) -> Option> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 41a9d65069c34f8c39934487cea286dbd55e940c..44e1523bd17c7692eb4665e4a038114444a11236 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -45,7 +45,7 @@ use language::{ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, - DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, + DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; use postage::watch; @@ -2442,22 +2442,23 @@ impl Project { } let server_id = pending_server.server_id; + let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); let key = key.clone(); - cx.spawn_weak(|this, cx| async move { + cx.spawn_weak(|this, mut cx| async move { let result = Self::setup_and_insert_language_server( this, initialization_options, pending_server, - adapter, + adapter.clone(), languages, language, server_id, key, - cx, + &mut cx, ) .await; @@ -2465,8 +2466,24 @@ impl Project { Ok(server) => Some(server), Err(err) => { - log::warn!("Error starting language server {:?}: {}", server_name, err); - // TODO: Prompt installation validity check LSP ERROR + println!("failed to start language server {:?}: {}", server_name, err); + + if let Some(this) = this.upgrade(&cx) { + if let Some(container_dir) = container_dir { + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; + + this.update(&mut cx, |_, cx| { + Self::check_errored_server_id( + server_id, + installation_test_binary, + cx, + ) + }); + } + } + None } } @@ -2482,6 +2499,7 @@ impl Project { server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { + println!("starting to reinstall server"); let (adapter, language, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { adapter, @@ -2495,6 +2513,7 @@ impl Project { Some(cx.spawn(move |this, mut cx| async move { if let Some(task) = server.shutdown() { + println!("shutting down existing server"); task.await; } @@ -2516,6 +2535,7 @@ impl Project { let worktree_id = worktree.id(); let root_path = worktree.abs_path(); + println!("prompting server start: {:?}", &adapter.name.0); this.start_language_server( worktree_id, root_path, @@ -2537,7 +2557,7 @@ impl Project { language: Arc, server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - mut cx: AsyncAppContext, + cx: &mut AsyncAppContext, ) -> Result> { let language_server = Self::setup_pending_language_server( this, @@ -2546,16 +2566,16 @@ impl Project { adapter.clone(), languages, server_id, - &mut cx, + cx, ) .await?; - let this = match this.upgrade(&mut cx) { + let this = match this.upgrade(cx) { Some(this) => this, None => return Err(anyhow!("failed to upgrade project handle")), }; - this.update(&mut cx, |this, cx| { + this.update(cx, |this, cx| { this.insert_newly_running_language_server( language, adapter, @@ -3012,32 +3032,39 @@ impl Project { .detach(); } - fn check_errored_lsp_installation( + fn check_errored_language_server( &self, language_server: Arc, cx: &mut ModelContext, ) { - cx.spawn(|this, mut cx| async move { - if !language_server.is_dead() { - return; - } - let server_id = language_server.server_id(); + if !language_server.is_dead() { + return; + } + + let server_id = language_server.server_id(); + let installation_test_binary = language_server.installation_test_binary().clone(); + Self::check_errored_server_id(server_id, installation_test_binary, cx); + } + fn check_errored_server_id( + server_id: LanguageServerId, + installation_test_binary: Option, + cx: &mut ModelContext, + ) { + cx.spawn(|this, mut cx| async move { + println!("About to spawn test binary"); // A lack of test binary counts as a failure - let process = language_server - .test_installation_binary() - .as_ref() - .and_then(|binary| { - smol::process::Command::new(&binary.path) - .current_dir(&binary.path) - .args(&binary.arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .kill_on_drop(true) - .spawn() - .ok() - }); + let process = installation_test_binary.and_then(|binary| { + smol::process::Command::new(&binary.path) + .current_dir(&binary.path) + .args(binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn() + .ok() + }); const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); @@ -3046,13 +3073,14 @@ impl Project { if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { - Ok(status) => errored = !status.success(), + Ok(status) => errored = !dbg!(status.success()), Err(_) => errored = true, }, - _ = timeout => {} + _ = timeout => { println!("test binary time-ed out"); } } } else { + println!("test binary failed to launch"); errored = true; } @@ -3883,7 +3911,7 @@ impl Project { ); this.update(cx, |this, cx| { - this.check_errored_lsp_installation(language_server.clone(), cx); + this.check_errored_language_server(language_server.clone(), cx); }); None From afa1434aa94ae7073ebf4575c81198283a2e6c25 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 17:45:27 -0400 Subject: [PATCH 007/169] Get further reinstalling a server which died on startup --- crates/project/src/project.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 44e1523bd17c7692eb4665e4a038114444a11236..5a42399040cec41a9e691f69dbf45a456fff265d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -280,7 +280,13 @@ pub enum Event { pub enum LanguageServerState { Validating(Task>>), - Starting(Task>>), + + Starting { + language: Arc, + adapter: Arc, + task: Task>>, + }, + Running { language: Arc, adapter: Arc, @@ -2443,9 +2449,11 @@ impl Project { let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); - let state = LanguageServerState::Starting({ + let task = { + let adapter = adapter.clone(); let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); + let language = language.clone(); let key = key.clone(); cx.spawn_weak(|this, mut cx| async move { @@ -2488,7 +2496,12 @@ impl Project { } } }) - }); + }; + let state = LanguageServerState::Starting { + language, + adapter, + task, + }; self.language_servers.insert(server_id, state); self.language_server_ids.insert(key, server_id); @@ -2500,19 +2513,23 @@ impl Project { cx: &mut ModelContext, ) -> Option> { println!("starting to reinstall server"); - let (adapter, language, server) = match self.language_servers.remove(&server_id) { + let (language, adapter, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { - adapter, language, + adapter, server, .. - }) => (adapter.clone(), language.clone(), server), + }) => (language.clone(), adapter.clone(), Some(server)), + + Some(LanguageServerState::Starting { + language, adapter, .. + }) => (language.clone(), adapter.clone(), None), _ => return None, }; Some(cx.spawn(move |this, mut cx| async move { - if let Some(task) = server.shutdown() { + if let Some(task) = server.and_then(|server| server.shutdown()) { println!("shutting down existing server"); task.await; } @@ -2921,7 +2938,7 @@ impl Project { let server = match server_state { Some(LanguageServerState::Validating(task)) => task.await, - Some(LanguageServerState::Starting(task)) => task.await, + Some(LanguageServerState::Starting { task, .. }) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -7425,7 +7442,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting(task) | Validating(task) => task.await?.shutdown()?.await, + Starting { task, .. } | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 7e70e24bfc92ac10cb64b464951e35ed56c8d690 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 19 Jun 2023 18:02:57 -0400 Subject: [PATCH 008/169] Remove server from both hashmaps --- crates/project/src/project.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5a42399040cec41a9e691f69dbf45a456fff265d..490d9fff800064bbcaf2e6f02d827875d27e7890 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2528,6 +2528,13 @@ impl Project { _ => return None, }; + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + let key = (worktree.read(cx).id(), adapter.name.clone()); + self.language_server_ids.remove(&key); + } + } + Some(cx.spawn(move |this, mut cx| async move { if let Some(task) = server.and_then(|server| server.shutdown()) { println!("shutting down existing server"); From e15be61ded150c1f3f8433077111a40421593d53 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 21 Jun 2023 14:02:21 -0400 Subject: [PATCH 009/169] The log-ification --- crates/language/src/language.rs | 7 +++---- crates/project/src/project.rs | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b9c2b52f9847010b6bd20cb6a18f9ace664b567b..e0ff6254445150ca93d88bb14f0a2bcfaaa5624d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -869,7 +869,7 @@ impl LanguageRegistry { cx: &mut AppContext, ) -> Option { let server_id = self.state.write().next_language_server_id(); - println!( + log::info!( "starting language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -954,7 +954,6 @@ impl LanguageRegistry { task.await?; } - println!("starting server"); let server = lsp::LanguageServer::new( server_id, binaries, @@ -985,7 +984,8 @@ impl LanguageRegistry { adapter: Arc, cx: &mut AppContext, ) -> Task<()> { - println!("deleting server container"); + log::info!("deleting server container"); + let mut lock = self.lsp_binary_paths.lock(); lock.remove(&adapter.name); @@ -1066,7 +1066,6 @@ async fn get_binaries( task.await?; } - println!("fetching binary"); let binary = fetch_latest_binary( adapter.clone(), language.clone(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e0ce91e772e7ed2d81bbb176bec6592fd49e1710..2536c94225f719b9153dc42ae7ec76323438d9f6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2480,7 +2480,7 @@ impl Project { Ok(server) => Some(server), Err(err) => { - println!("failed to start language server {:?}: {}", server_name, err); + log::error!("failed to start language server {:?}: {}", server_name, err); if let Some(this) = this.upgrade(&cx) { if let Some(container_dir) = container_dir { @@ -2518,7 +2518,7 @@ impl Project { server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { - println!("starting to reinstall server"); + log::info!("beginning to reinstall server"); let (language, adapter, server) = match self.language_servers.remove(&server_id) { Some(LanguageServerState::Running { language, @@ -2543,7 +2543,7 @@ impl Project { Some(cx.spawn(move |this, mut cx| async move { if let Some(task) = server.and_then(|server| server.shutdown()) { - println!("shutting down existing server"); + log::info!("shutting down existing server"); task.await; } @@ -2565,7 +2565,6 @@ impl Project { let worktree_id = worktree.id(); let root_path = worktree.abs_path(); - println!("prompting server start: {:?}", &adapter.name.0); this.start_language_server( worktree_id, root_path, @@ -3082,7 +3081,8 @@ impl Project { cx: &mut ModelContext, ) { cx.spawn(|this, mut cx| async move { - println!("About to spawn test binary"); + log::info!("About to spawn test binary"); + // A lack of test binary counts as a failure let process = installation_test_binary.and_then(|binary| { smol::process::Command::new(&binary.path) @@ -3103,18 +3103,21 @@ impl Project { if let Some(mut process) = process { futures::select! { status = process.status().fuse() => match status { - Ok(status) => errored = !dbg!(status.success()), + Ok(status) => errored = !status.success(), Err(_) => errored = true, }, - _ = timeout => { println!("test binary time-ed out"); } + _ = timeout => { + log::info!("test binary time-ed out, this counts as a success"); + } } } else { - println!("test binary failed to launch"); + log::warn!("test binary failed to launch"); errored = true; } if errored { + log::warn!("test binary check failed"); let task = this.update(&mut cx, move |this, mut cx| { this.reinstall_language_server(server_id, &mut cx) }); From 9b63d6f832650c433cebd911c8b7863b062cdc1f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 21 Jun 2023 23:05:37 -0400 Subject: [PATCH 010/169] Route language server requests through wrapper object --- crates/lsp/src/lsp.rs | 4 +- crates/project/src/lsp_command.rs | 28 +++--- crates/project/src/project.rs | 162 +++++++++++++++++++++++------- 3 files changed, 142 insertions(+), 52 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d52de667d42e668f43f959ff0fb42385ba3fcde3..e973a77f4fe211923a6aa9cb59d040bf4c606e96 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -665,11 +665,11 @@ impl LanguageServer { } } - pub fn name<'a>(self: &'a Arc) -> &'a str { + pub fn name<'a>(&self) -> &str { &self.name } - pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { + pub fn capabilities<'a>(&self) -> &ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8435de71e2f420e479035a5d21b36e850db29834..82e2c0c5a561dcfc4eda4b1dd22d1aa79937d6ea 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,6 @@ use crate::{ DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectTransaction, + ProjectLanguageServer, ProjectTransaction, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -14,7 +14,7 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities}; +use lsp::{DocumentHighlightKind, LanguageServerId, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { @@ -40,7 +40,7 @@ pub(crate) trait LspCommand: 'static + Sized { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, cx: &AppContext, ) -> ::Params; @@ -156,7 +156,7 @@ impl LspCommand for PrepareRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::TextDocumentPositionParams { lsp::TextDocumentPositionParams { @@ -279,7 +279,7 @@ impl LspCommand for PerformRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::RenameParams { lsp::RenameParams { @@ -398,7 +398,7 @@ impl LspCommand for GetDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoDefinitionParams { lsp::GotoDefinitionParams { @@ -499,7 +499,7 @@ impl LspCommand for GetTypeDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoTypeDefinitionParams { lsp::GotoTypeDefinitionParams { @@ -587,7 +587,7 @@ fn language_server_for_buffer( buffer: &ModelHandle, server_id: LanguageServerId, cx: &mut AsyncAppContext, -) -> Result<(Arc, Arc)> { +) -> Result<(Arc, Arc)> { project .read_with(cx, |project, cx| { project @@ -784,7 +784,7 @@ impl LspCommand for GetReferences { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::ReferenceParams { lsp::ReferenceParams { @@ -949,7 +949,7 @@ impl LspCommand for GetDocumentHighlights { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { @@ -1096,7 +1096,7 @@ impl LspCommand for GetHover { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::HoverParams { lsp::HoverParams { @@ -1314,7 +1314,7 @@ impl LspCommand for GetCompletions { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::CompletionParams { lsp::CompletionParams { @@ -1530,7 +1530,7 @@ impl LspCommand for GetCodeActions { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, _: &AppContext, ) -> lsp::CodeActionParams { let relevant_diagnostics = buffer @@ -1673,7 +1673,7 @@ impl LspCommand for OnTypeFormatting { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentOnTypeFormattingParams { lsp::DocumentOnTypeFormattingParams { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2536c94225f719b9153dc42ae7ec76323438d9f6..5c22a14d4d919bb412110c3570bd43dec11245aa 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -279,19 +279,79 @@ pub enum Event { CollaboratorLeft(proto::PeerId), } +pub struct ProjectLanguageServer { + server: Arc, + project: WeakModelHandle, +} + +impl std::ops::Deref for ProjectLanguageServer { + type Target = LanguageServer; + + fn deref(&self) -> &Self::Target { + &self.server + } +} + +impl ProjectLanguageServer { + pub fn new(server: Arc, project: WeakModelHandle) -> Self { + ProjectLanguageServer { server, project } + } + + pub fn request( + &self, + params: T::Params, + cx: &mut AsyncAppContext, + ) -> impl Future> + where + T::Result: 'static + Send, + { + let server = self.server.clone(); + let project = self.project.clone(); + + let future = server.request::(params); + + cx.spawn(|mut cx| async move { + let result = future.await; + if result.is_ok() { + return result; + } + + let project = match project.upgrade(&cx) { + Some(project) => project, + None => return result, + }; + + project.update(&mut cx, |_, cx| { + Project::check_errored_language_server(server, cx); + }); + + result + }) + } + + pub fn notify(&self, params: T::Params) -> Result<()> { + let result = self.server.notify::(params); + if result.is_ok() { + return Ok(()); + } + + result + } +} + pub enum LanguageServerState { - Validating(Task>>), + Validating(Task>>), Starting { language: Arc, adapter: Arc, - task: Task>>, + task: Task>>, }, Running { language: Arc, adapter: Arc, - server: Arc, + server: Arc, watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, @@ -2237,7 +2297,13 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, &Arc, &Arc)> { + ) -> impl Iterator< + Item = ( + &Arc, + &Arc, + &Arc, + ), + > { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { @@ -2477,7 +2543,7 @@ impl Project { .await; match result { - Ok(server) => Some(server), + Ok(server) => Some(Arc::new(ProjectLanguageServer::new(server, this))), Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2803,7 +2869,10 @@ impl Project { adapter: adapter.clone(), language: language.clone(), watched_paths: Default::default(), - server: language_server.clone(), + server: Arc::new(ProjectLanguageServer::new( + language_server.clone(), + cx.weak_handle(), + )), simulate_disk_based_diagnostics_completion: None, }, ); @@ -3062,7 +3131,6 @@ impl Project { } fn check_errored_language_server( - &self, language_server: Arc, cx: &mut ModelContext, ) { @@ -3897,7 +3965,7 @@ impl Project { this: &ModelHandle, buffer: &ModelHandle, abs_path: &Path, - language_server: &Arc, + language_server: &Arc, tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { @@ -3911,23 +3979,29 @@ impl Project { let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server - .request::(lsp::DocumentFormattingParams { - text_document, - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }) + .request::( + lsp::DocumentFormattingParams { + text_document, + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }, + cx, + ) .await } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); language_server - .request::(lsp::DocumentRangeFormattingParams { - text_document, - range: lsp::Range::new(buffer_start, buffer_end), - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }) + .request::( + lsp::DocumentRangeFormattingParams { + text_document, + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }, + cx, + ) .await } else { Ok(None) @@ -3943,8 +4017,8 @@ impl Project { err ); - this.update(cx, |this, cx| { - this.check_errored_language_server(language_server.clone(), cx); + this.update(cx, |_, cx| { + Self::check_errored_language_server(language_server.server.clone(), cx); }); None @@ -4090,6 +4164,7 @@ impl Project { query: query.to_string(), ..Default::default() }, + &mut cx.to_async(), ) .map_ok(move |response| { let lsp_symbols = response.map(|symbol_response| match symbol_response { @@ -4323,7 +4398,10 @@ impl Project { cx.spawn(|this, mut cx| async move { let resolved_completion = match lang_server - .request::(completion.lsp_completion) + .request::( + completion.lsp_completion, + &mut cx, + ) .await { Ok(resolved_completion) => resolved_completion, @@ -4452,7 +4530,10 @@ impl Project { { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); action.lsp_action = match lang_server - .request::(action.lsp_action) + .request::( + action.lsp_action, + &mut cx, + ) .await { Ok(lsp_action) => lsp_action, @@ -4496,11 +4577,14 @@ impl Project { }); let result = lang_server - .request::(lsp::ExecuteCommandParams { - command: command.command, - arguments: command.arguments.unwrap_or_default(), - ..Default::default() - }) + .request::( + lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + ..Default::default() + }, + &mut cx, + ) .await; if let Err(err) = result { @@ -4607,7 +4691,7 @@ impl Project { edits: Vec, push_to_history: bool, _: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result> { let edits = this @@ -4648,7 +4732,7 @@ impl Project { edit: lsp::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { let fs = this.read_with(cx, |this, _| this.fs.clone()); @@ -5070,12 +5154,15 @@ impl Project { .map(|(_, server)| server.clone()), ) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); - return cx.spawn(|this, cx| async move { + return cx.spawn(|this, mut cx| async move { if !request.check_capabilities(language_server.capabilities()) { return Ok(Default::default()); } - let result = language_server.request::(lsp_params).await; + let result = language_server + .request::(lsp_params, &mut cx) + .await; + let response = match result { Ok(response) => response, @@ -7277,7 +7364,10 @@ impl Project { }) } - pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { + pub fn language_server_for_id( + &self, + id: LanguageServerId, + ) -> Option> { if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? { Some(server.clone()) } else { @@ -7289,7 +7379,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() .filter_map(|server_id| { @@ -7309,7 +7399,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx).next() } @@ -7318,7 +7408,7 @@ impl Project { buffer: &Buffer, server_id: LanguageServerId, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx) .find(|(_, s)| s.server_id() == server_id) } From e1cd6cebb9542216e6101747c6da6e1f14722331 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 10:45:08 -0400 Subject: [PATCH 011/169] Revert "Route language server requests through wrapper object" This reverts commit 9b63d6f832650c433cebd911c8b7863b062cdc1f. --- crates/lsp/src/lsp.rs | 4 +- crates/project/src/lsp_command.rs | 28 +++--- crates/project/src/project.rs | 162 +++++++----------------------- 3 files changed, 52 insertions(+), 142 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index e973a77f4fe211923a6aa9cb59d040bf4c606e96..d52de667d42e668f43f959ff0fb42385ba3fcde3 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -665,11 +665,11 @@ impl LanguageServer { } } - pub fn name<'a>(&self) -> &str { + pub fn name<'a>(self: &'a Arc) -> &'a str { &self.name } - pub fn capabilities<'a>(&self) -> &ServerCapabilities { + pub fn capabilities<'a>(self: &'a Arc) -> &'a ServerCapabilities { &self.capabilities } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 82e2c0c5a561dcfc4eda4b1dd22d1aa79937d6ea..8435de71e2f420e479035a5d21b36e850db29834 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,6 @@ use crate::{ DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectLanguageServer, ProjectTransaction, + ProjectTransaction, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -14,7 +14,7 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp::{DocumentHighlightKind, LanguageServerId, ServerCapabilities}; +use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { @@ -40,7 +40,7 @@ pub(crate) trait LspCommand: 'static + Sized { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, cx: &AppContext, ) -> ::Params; @@ -156,7 +156,7 @@ impl LspCommand for PrepareRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::TextDocumentPositionParams { lsp::TextDocumentPositionParams { @@ -279,7 +279,7 @@ impl LspCommand for PerformRename { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::RenameParams { lsp::RenameParams { @@ -398,7 +398,7 @@ impl LspCommand for GetDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoDefinitionParams { lsp::GotoDefinitionParams { @@ -499,7 +499,7 @@ impl LspCommand for GetTypeDefinition { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::GotoTypeDefinitionParams { lsp::GotoTypeDefinitionParams { @@ -587,7 +587,7 @@ fn language_server_for_buffer( buffer: &ModelHandle, server_id: LanguageServerId, cx: &mut AsyncAppContext, -) -> Result<(Arc, Arc)> { +) -> Result<(Arc, Arc)> { project .read_with(cx, |project, cx| { project @@ -784,7 +784,7 @@ impl LspCommand for GetReferences { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::ReferenceParams { lsp::ReferenceParams { @@ -949,7 +949,7 @@ impl LspCommand for GetDocumentHighlights { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentHighlightParams { lsp::DocumentHighlightParams { @@ -1096,7 +1096,7 @@ impl LspCommand for GetHover { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::HoverParams { lsp::HoverParams { @@ -1314,7 +1314,7 @@ impl LspCommand for GetCompletions { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::CompletionParams { lsp::CompletionParams { @@ -1530,7 +1530,7 @@ impl LspCommand for GetCodeActions { &self, path: &Path, buffer: &Buffer, - language_server: &Arc, + language_server: &Arc, _: &AppContext, ) -> lsp::CodeActionParams { let relevant_diagnostics = buffer @@ -1673,7 +1673,7 @@ impl LspCommand for OnTypeFormatting { &self, path: &Path, _: &Buffer, - _: &Arc, + _: &Arc, _: &AppContext, ) -> lsp::DocumentOnTypeFormattingParams { lsp::DocumentOnTypeFormattingParams { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c22a14d4d919bb412110c3570bd43dec11245aa..2536c94225f719b9153dc42ae7ec76323438d9f6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -279,79 +279,19 @@ pub enum Event { CollaboratorLeft(proto::PeerId), } -pub struct ProjectLanguageServer { - server: Arc, - project: WeakModelHandle, -} - -impl std::ops::Deref for ProjectLanguageServer { - type Target = LanguageServer; - - fn deref(&self) -> &Self::Target { - &self.server - } -} - -impl ProjectLanguageServer { - pub fn new(server: Arc, project: WeakModelHandle) -> Self { - ProjectLanguageServer { server, project } - } - - pub fn request( - &self, - params: T::Params, - cx: &mut AsyncAppContext, - ) -> impl Future> - where - T::Result: 'static + Send, - { - let server = self.server.clone(); - let project = self.project.clone(); - - let future = server.request::(params); - - cx.spawn(|mut cx| async move { - let result = future.await; - if result.is_ok() { - return result; - } - - let project = match project.upgrade(&cx) { - Some(project) => project, - None => return result, - }; - - project.update(&mut cx, |_, cx| { - Project::check_errored_language_server(server, cx); - }); - - result - }) - } - - pub fn notify(&self, params: T::Params) -> Result<()> { - let result = self.server.notify::(params); - if result.is_ok() { - return Ok(()); - } - - result - } -} - pub enum LanguageServerState { - Validating(Task>>), + Validating(Task>>), Starting { language: Arc, adapter: Arc, - task: Task>>, + task: Task>>, }, Running { language: Arc, adapter: Arc, - server: Arc, + server: Arc, watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, @@ -2297,13 +2237,7 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator< - Item = ( - &Arc, - &Arc, - &Arc, - ), - > { + ) -> impl Iterator, &Arc, &Arc)> { self.language_server_ids .iter() .filter_map(move |((language_server_worktree_id, _), id)| { @@ -2543,7 +2477,7 @@ impl Project { .await; match result { - Ok(server) => Some(Arc::new(ProjectLanguageServer::new(server, this))), + Ok(server) => Some(server), Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2869,10 +2803,7 @@ impl Project { adapter: adapter.clone(), language: language.clone(), watched_paths: Default::default(), - server: Arc::new(ProjectLanguageServer::new( - language_server.clone(), - cx.weak_handle(), - )), + server: language_server.clone(), simulate_disk_based_diagnostics_completion: None, }, ); @@ -3131,6 +3062,7 @@ impl Project { } fn check_errored_language_server( + &self, language_server: Arc, cx: &mut ModelContext, ) { @@ -3965,7 +3897,7 @@ impl Project { this: &ModelHandle, buffer: &ModelHandle, abs_path: &Path, - language_server: &Arc, + language_server: &Arc, tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { @@ -3979,29 +3911,23 @@ impl Project { let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server - .request::( - lsp::DocumentFormattingParams { - text_document, - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }, - cx, - ) + .request::(lsp::DocumentFormattingParams { + text_document, + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }) .await } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); language_server - .request::( - lsp::DocumentRangeFormattingParams { - text_document, - range: lsp::Range::new(buffer_start, buffer_end), - options: lsp_command::lsp_formatting_options(tab_size.get()), - work_done_progress_params: Default::default(), - }, - cx, - ) + .request::(lsp::DocumentRangeFormattingParams { + text_document, + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp_command::lsp_formatting_options(tab_size.get()), + work_done_progress_params: Default::default(), + }) .await } else { Ok(None) @@ -4017,8 +3943,8 @@ impl Project { err ); - this.update(cx, |_, cx| { - Self::check_errored_language_server(language_server.server.clone(), cx); + this.update(cx, |this, cx| { + this.check_errored_language_server(language_server.clone(), cx); }); None @@ -4164,7 +4090,6 @@ impl Project { query: query.to_string(), ..Default::default() }, - &mut cx.to_async(), ) .map_ok(move |response| { let lsp_symbols = response.map(|symbol_response| match symbol_response { @@ -4398,10 +4323,7 @@ impl Project { cx.spawn(|this, mut cx| async move { let resolved_completion = match lang_server - .request::( - completion.lsp_completion, - &mut cx, - ) + .request::(completion.lsp_completion) .await { Ok(resolved_completion) => resolved_completion, @@ -4530,10 +4452,7 @@ impl Project { { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); action.lsp_action = match lang_server - .request::( - action.lsp_action, - &mut cx, - ) + .request::(action.lsp_action) .await { Ok(lsp_action) => lsp_action, @@ -4577,14 +4496,11 @@ impl Project { }); let result = lang_server - .request::( - lsp::ExecuteCommandParams { - command: command.command, - arguments: command.arguments.unwrap_or_default(), - ..Default::default() - }, - &mut cx, - ) + .request::(lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + ..Default::default() + }) .await; if let Err(err) = result { @@ -4691,7 +4607,7 @@ impl Project { edits: Vec, push_to_history: bool, _: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result> { let edits = this @@ -4732,7 +4648,7 @@ impl Project { edit: lsp::WorkspaceEdit, push_to_history: bool, lsp_adapter: Arc, - language_server: Arc, + language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { let fs = this.read_with(cx, |this, _| this.fs.clone()); @@ -5154,15 +5070,12 @@ impl Project { .map(|(_, server)| server.clone()), ) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); - return cx.spawn(|this, mut cx| async move { + return cx.spawn(|this, cx| async move { if !request.check_capabilities(language_server.capabilities()) { return Ok(Default::default()); } - let result = language_server - .request::(lsp_params, &mut cx) - .await; - + let result = language_server.request::(lsp_params).await; let response = match result { Ok(response) => response, @@ -7364,10 +7277,7 @@ impl Project { }) } - pub fn language_server_for_id( - &self, - id: LanguageServerId, - ) -> Option> { + pub fn language_server_for_id(&self, id: LanguageServerId) -> Option> { if let LanguageServerState::Running { server, .. } = self.language_servers.get(&id)? { Some(server.clone()) } else { @@ -7379,7 +7289,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> impl Iterator, &Arc)> { + ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() .filter_map(|server_id| { @@ -7399,7 +7309,7 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx).next() } @@ -7408,7 +7318,7 @@ impl Project { buffer: &Buffer, server_id: LanguageServerId, cx: &AppContext, - ) -> Option<(&Arc, &Arc)> { + ) -> Option<(&Arc, &Arc)> { self.language_servers_for_buffer(buffer, cx) .find(|(_, s)| s.server_id() == server_id) } From 0abda54d3cb5d1576e66765ccca28fd048200fea Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 11:18:17 -0400 Subject: [PATCH 012/169] Remove individual location's request error handling --- crates/lsp/src/lsp.rs | 17 +------ crates/project/src/project.rs | 93 +++++++---------------------------- 2 files changed, 21 insertions(+), 89 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d52de667d42e668f43f959ff0fb42385ba3fcde3..deab1d9cc06c4343e372f8930476cacd2f2f4b99 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -64,7 +64,7 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, - server: Option>, + _server: Option>, installation_test_binary: Option, } @@ -247,24 +247,11 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - server: server.map(|server| Mutex::new(server)), + _server: server.map(|server| Mutex::new(server)), installation_test_binary, } } - pub fn is_dead(&self) -> bool { - let server = match self.server.as_ref() { - Some(server) => server, - None => return false, // Fake server for tests - }; - - match server.lock().try_status() { - Ok(Some(_)) => true, - Ok(None) => false, - Err(_) => true, - } - } - pub fn installation_test_binary(&self) -> &Option { &self.installation_test_binary } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2536c94225f719b9153dc42ae7ec76323438d9f6..46c8690555c7b33823b5b53983dcc14901bf9438 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3061,20 +3061,6 @@ impl Project { .detach(); } - fn check_errored_language_server( - &self, - language_server: Arc, - cx: &mut ModelContext, - ) { - if !language_server.is_dead() { - return; - } - - let server_id = language_server.server_id(); - let installation_test_binary = language_server.installation_test_binary().clone(); - Self::check_errored_server_id(server_id, installation_test_binary, cx); - } - fn check_errored_server_id( server_id: LanguageServerId, installation_test_binary: Option, @@ -3909,14 +3895,14 @@ impl Project { let formatting_provider = capabilities.document_formatting_provider.as_ref(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - let result = if !matches!(formatting_provider, Some(OneOf::Left(false))) { + let lsp_edits = if !matches!(formatting_provider, Some(OneOf::Left(false))) { language_server .request::(lsp::DocumentFormattingParams { text_document, options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await + .await? } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); @@ -3928,27 +3914,9 @@ impl Project { options: lsp_command::lsp_formatting_options(tab_size.get()), work_done_progress_params: Default::default(), }) - .await + .await? } else { - Ok(None) - }; - - let lsp_edits = match result { - Ok(lsp_edits) => lsp_edits, - - Err(err) => { - log::warn!( - "Error firing format request to {}: {}", - language_server.name(), - err - ); - - this.update(cx, |this, cx| { - this.check_errored_language_server(language_server.clone(), cx); - }); - - None - } + None }; if let Some(lsp_edits) = lsp_edits { @@ -4091,8 +4059,9 @@ impl Project { ..Default::default() }, ) - .map_ok(move |response| { - let lsp_symbols = response.map(|symbol_response| match symbol_response { + .log_err() + .map(move |response| { + let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { flat_responses.into_iter().map(|lsp_symbol| { (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) @@ -4132,22 +4101,14 @@ impl Project { let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); - for response in responses { - let ( - adapter, - adapter_language, - source_worktree_id, - worktree_abs_path, - lsp_symbols, - ) = match response { - Ok(response) => response, - - Err(err) => { - // TODO: Prompt installation validity check LSP ERROR - return Vec::new(); - } - }; - + for ( + adapter, + adapter_language, + source_worktree_id, + worktree_abs_path, + lsp_symbols, + ) in responses + { symbols.extend(lsp_symbols.into_iter().filter_map( |(symbol_name, symbol_kind, symbol_location)| { let abs_path = symbol_location.uri.to_file_path().ok()?; @@ -4322,17 +4283,9 @@ impl Project { }; cx.spawn(|this, mut cx| async move { - let resolved_completion = match lang_server + let resolved_completion = lang_server .request::(completion.lsp_completion) - .await - { - Ok(resolved_completion) => resolved_completion, - - Err(err) => { - // TODO: LSP ERROR - return Ok(None); - } - }; + .await?; if let Some(edits) = resolved_completion.additional_text_edits { let edits = this @@ -4451,17 +4404,9 @@ impl Project { .and_then(|d| d.get_mut("range")) { *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); - action.lsp_action = match lang_server + action.lsp_action = lang_server .request::(action.lsp_action) - .await - { - Ok(lsp_action) => lsp_action, - - Err(err) => { - // LSP ERROR - return Err(err); - } - }; + .await?; } else { let actions = this .update(&mut cx, |this, cx| { From a8acf2898980cf6b46395d7f8bebf1b35fe4e2eb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 20:19:07 -0400 Subject: [PATCH 013/169] Remove now-unnecessary complexity --- crates/project/src/project.rs | 46 +++++++++++++---------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 46c8690555c7b33823b5b53983dcc14901bf9438..60fd95e007d799fcdbb077b8f85ee77ae096abb6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -282,11 +282,7 @@ pub enum Event { pub enum LanguageServerState { Validating(Task>>), - Starting { - language: Arc, - adapter: Arc, - task: Task>>, - }, + Starting(Task>>), Running { language: Arc, @@ -2455,7 +2451,7 @@ impl Project { let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); - let task = { + let state = LanguageServerState::Starting({ let adapter = adapter.clone(); let server_name = adapter.name.0.clone(); let languages = self.languages.clone(); @@ -2469,7 +2465,7 @@ impl Project { pending_server, adapter.clone(), languages, - language, + language.clone(), server_id, key, &mut cx, @@ -2490,6 +2486,8 @@ impl Project { this.update(&mut cx, |_, cx| { Self::check_errored_server_id( + language, + adapter, server_id, installation_test_binary, cx, @@ -2502,12 +2500,7 @@ impl Project { } } }) - }; - let state = LanguageServerState::Starting { - language, - adapter, - task, - }; + }); self.language_servers.insert(server_id, state); self.language_server_ids.insert(key, server_id); @@ -2515,23 +2508,16 @@ impl Project { fn reinstall_language_server( &mut self, + language: Arc, + adapter: Arc, server_id: LanguageServerId, cx: &mut ModelContext, ) -> Option> { log::info!("beginning to reinstall server"); - let (language, adapter, server) = match self.language_servers.remove(&server_id) { - Some(LanguageServerState::Running { - language, - adapter, - server, - .. - }) => (language.clone(), adapter.clone(), Some(server)), - Some(LanguageServerState::Starting { - language, adapter, .. - }) => (language.clone(), adapter.clone(), None), - - _ => return None, + let existing_server = match self.language_servers.remove(&server_id) { + Some(LanguageServerState::Running { server, .. }) => Some(server), + _ => None, }; for worktree in &self.worktrees { @@ -2542,7 +2528,7 @@ impl Project { } Some(cx.spawn(move |this, mut cx| async move { - if let Some(task) = server.and_then(|server| server.shutdown()) { + if let Some(task) = existing_server.and_then(|server| server.shutdown()) { log::info!("shutting down existing server"); task.await; } @@ -2950,7 +2936,7 @@ impl Project { let server = match server_state { Some(LanguageServerState::Validating(task)) => task.await, - Some(LanguageServerState::Starting { task, .. }) => task.await, + Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -3062,6 +3048,8 @@ impl Project { } fn check_errored_server_id( + language: Arc, + adapter: Arc, server_id: LanguageServerId, installation_test_binary: Option, cx: &mut ModelContext, @@ -3105,7 +3093,7 @@ impl Project { if errored { log::warn!("test binary check failed"); let task = this.update(&mut cx, move |this, mut cx| { - this.reinstall_language_server(server_id, &mut cx) + this.reinstall_language_server(language, adapter, server_id, &mut cx) }); if let Some(task) = task { @@ -7403,7 +7391,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting { task, .. } | Validating(task) => task.await?.shutdown()?.await, + Starting(task) | Validating(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 3302e1133f4ffef9c5c022462b55aede2dc41dbb Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 22 Jun 2023 20:22:05 -0400 Subject: [PATCH 014/169] Whoops --- styles/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/tsconfig.json b/styles/tsconfig.json index fe9682b9697ceaf53023527e668eb836a80a5c44..6d24728a0a9f0e38b3a038c7c7bf3d0642e6e88f 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -1,5 +1,4 @@ { - // "noErrorTruncation": true, "compilerOptions": { "target": "es2015", "module": "commonjs", From 374c1a3a3e9f872c70c0051764835b8d7e99454d Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 23 Jun 2023 00:17:27 -0400 Subject: [PATCH 015/169] Remove some status stuff --- crates/activity_indicator/src/activity_indicator.rs | 4 +--- crates/language/src/language.rs | 1 - crates/project/src/project.rs | 9 +++------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index a8874e8d9e89f42f03f3f47d493a773dd4619f7b..9172b84f3c2cec4111777f8e1e974ea2a6d8f8a7 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -203,14 +203,12 @@ impl ActivityIndicator { } // Show any language server installation info. - let mut validating = SmallVec::<[_; 3]>::new(); let mut downloading = SmallVec::<[_; 3]>::new(); let mut checking_for_update = SmallVec::<[_; 3]>::new(); let mut failed = SmallVec::<[_; 3]>::new(); for status in &self.statuses { let name = status.name.clone(); match status.status { - LanguageServerBinaryStatus::Validating => validating.push(name), LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name), LanguageServerBinaryStatus::Downloading => downloading.push(name), LanguageServerBinaryStatus::Failed { .. } => failed.push(name), @@ -242,7 +240,7 @@ impl ActivityIndicator { ), on_click: None, }; - } else if !failed.is_empty() || !validating.is_empty() { + } else if !failed.is_empty() { return Content { icon: Some(WARNING_ICON), message: format!( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e0ff6254445150ca93d88bb14f0a2bcfaaa5624d..9e3d3708ba7bc37ab81584cc68f3b19713bb0589 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -536,7 +536,6 @@ struct BracketConfig { #[derive(Clone)] pub enum LanguageServerBinaryStatus { - Validating, CheckingForUpdate, Downloading, Downloaded, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 60fd95e007d799fcdbb077b8f85ee77ae096abb6..a33a47779c24f27deff5e72645202e0df5831fa0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -280,8 +280,6 @@ pub enum Event { } pub enum LanguageServerState { - Validating(Task>>), - Starting(Task>>), Running { @@ -2485,7 +2483,7 @@ impl Project { .await; this.update(&mut cx, |_, cx| { - Self::check_errored_server_id( + Self::check_errored_server( language, adapter, server_id, @@ -2935,7 +2933,6 @@ impl Project { let mut root_path = None; let server = match server_state { - Some(LanguageServerState::Validating(task)) => task.await, Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, @@ -3047,7 +3044,7 @@ impl Project { .detach(); } - fn check_errored_server_id( + fn check_errored_server( language: Arc, adapter: Arc, server_id: LanguageServerId, @@ -7391,7 +7388,7 @@ impl Entity for Project { use LanguageServerState::*; match server_state { Running { server, .. } => server.shutdown()?.await, - Starting(task) | Validating(task) => task.await?.shutdown()?.await, + Starting(task) => task.await?.shutdown()?.await, } }) .collect::>(); From 7caa096bd002f8e18d42f76b012ab7acfa5e25bc Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 23 Jun 2023 13:24:50 -0400 Subject: [PATCH 016/169] Remove installation test binary from language server instance --- crates/copilot/src/copilot.rs | 8 ++----- crates/language/src/language.rs | 39 ++++++++++----------------------- crates/lsp/src/lsp.rs | 24 ++++---------------- crates/project/src/project.rs | 2 +- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 866bb8d7f526358a7cacb833c84037c87a23b878..4c45bf823bf8f77db9fbcea95e731e37475f4562 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -15,7 +15,7 @@ use language::{ ToPointUtf16, }; use log::{debug, error}; -use lsp::{LanguageServer, LanguageServerBinaries, LanguageServerBinary, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; use settings::SettingsStore; @@ -366,13 +366,9 @@ impl Copilot { path: node_path, arguments, }; - let binaries = LanguageServerBinaries { - binary: binary.clone(), - installation_test_binary: Some(binary), - }; let server = LanguageServer::new( LanguageServerId(0), - binaries, + binary, Path::new("/"), None, cx.clone(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 9e3d3708ba7bc37ab81584cc68f3b19713bb0589..beec63dfd2447da7be541f0475567e913cb8bd79 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, AsyncAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::{CodeActionKind, LanguageServerBinaries, LanguageServerBinary}; +use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -564,10 +564,7 @@ pub struct LanguageRegistry { login_shell_env_loaded: Shared>, #[allow(clippy::type_complexity)] lsp_binary_paths: Mutex< - HashMap< - LanguageServerName, - Shared>>>, - >, + HashMap>>>>, >, executor: Option>, } @@ -859,7 +856,7 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_pending_language_server( + pub fn create_pending_language_server( self: &Arc, language: Arc, adapter: Arc, @@ -932,7 +929,7 @@ impl LanguageRegistry { .entry(adapter.name.clone()) .or_insert_with(|| { cx.spawn(|cx| { - get_binaries( + get_binary( adapter.clone(), language.clone(), delegate.clone(), @@ -953,15 +950,13 @@ impl LanguageRegistry { task.await?; } - let server = lsp::LanguageServer::new( + lsp::LanguageServer::new( server_id, binaries, &root_path, adapter.code_action_kinds(), cx, - )?; - - Ok(server) + ) }) }; @@ -1047,14 +1042,14 @@ impl Default for LanguageRegistry { } } -async fn get_binaries( +async fn get_binary( adapter: Arc, language: Arc, delegate: Arc, container_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, mut cx: AsyncAppContext, -) -> Result { +) -> Result { if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -1082,13 +1077,7 @@ async fn get_binaries( statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - let installation_test_binary = adapter - .installation_test_binary(container_dir.to_path_buf()) - .await; - return Ok(LanguageServerBinaries { - binary, - installation_test_binary, - }); + return Ok(binary); } else { statuses .broadcast(( @@ -1110,7 +1099,7 @@ async fn fetch_latest_binary( delegate: &dyn LspAdapterDelegate, container_dir: &Path, lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, -) -> Result { +) -> Result { let container_dir: Arc = container_dir.into(); lsp_binary_statuses_tx .broadcast(( @@ -1127,17 +1116,11 @@ async fn fetch_latest_binary( let binary = adapter .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate) .await?; - let installation_test_binary = adapter - .installation_test_binary(container_dir.to_path_buf()) - .await; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; - Ok(LanguageServerBinaries { - binary, - installation_test_binary, - }) + Ok(binary) } impl Language { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index deab1d9cc06c4343e372f8930476cacd2f2f4b99..ffca2f24abf661056dc0c12bbcc69e31fdd5ca3d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -43,12 +43,6 @@ pub struct LanguageServerBinary { pub arguments: Vec, } -#[derive(Debug, Clone, Deserialize)] -pub struct LanguageServerBinaries { - pub binary: LanguageServerBinary, - pub installation_test_binary: Option, -} - pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -65,7 +59,6 @@ pub struct LanguageServer { output_done_rx: Mutex>, root_path: PathBuf, _server: Option>, - installation_test_binary: Option, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -135,7 +128,7 @@ struct Error { impl LanguageServer { pub fn new( server_id: LanguageServerId, - binaries: LanguageServerBinaries, + binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -146,9 +139,9 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(&binaries.binary.path) + let mut server = process::Command::new(&binary.path) .current_dir(working_dir) - .args(binaries.binary.arguments) + .args(binary.arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -162,7 +155,6 @@ impl LanguageServer { stdin, stout, Some(server), - binaries.installation_test_binary, root_path, code_action_kinds, cx, @@ -181,7 +173,7 @@ impl LanguageServer { }, ); - if let Some(name) = binaries.binary.path.file_name() { + if let Some(name) = binary.path.file_name() { server.name = name.to_string_lossy().to_string(); } @@ -193,7 +185,6 @@ impl LanguageServer { stdin: Stdin, stdout: Stdout, server: Option, - installation_test_binary: Option, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -248,14 +239,9 @@ impl LanguageServer { output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), _server: server.map(|server| Mutex::new(server)), - installation_test_binary, } } - pub fn installation_test_binary(&self) -> &Option { - &self.installation_test_binary - } - pub fn code_action_kinds(&self) -> Option> { self.code_action_kinds.clone() } @@ -840,7 +826,6 @@ impl LanguageServer { stdin_writer, stdout_reader, None, - None, Path::new("/"), None, cx.clone(), @@ -852,7 +837,6 @@ impl LanguageServer { stdout_writer, stdin_reader, None, - None, Path::new("/"), None, cx, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a33a47779c24f27deff5e72645202e0df5831fa0..4c6b25b0e90fa3a1367d5e60e4a8133e069c6f64 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2423,7 +2423,7 @@ impl Project { return; } - let pending_server = match self.languages.start_pending_language_server( + let pending_server = match self.languages.create_pending_language_server( language.clone(), adapter.clone(), worktree_path, From c4b83c86cc726417128a7dfa21ce1ec652573269 Mon Sep 17 00:00:00 2001 From: Julia Date: Sat, 24 Jun 2023 22:42:06 -0400 Subject: [PATCH 017/169] Avoid validating/reinstalling server which refuses will_fetch/start These adapters have indicated some broader reason to the user why they cannot be started, don't waste time/bandwidth attempting to validate and reinstall them --- crates/language/src/language.rs | 20 +++++++++++++------- crates/project/src/project.rs | 26 ++++++++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index beec63dfd2447da7be541f0475567e913cb8bd79..4f03da1afe7df3d81a53a1c6802eabf49c6bac05 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -583,7 +583,7 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, - pub task: Task>, + pub task: Task>>, pub container_dir: Option>, } @@ -896,7 +896,8 @@ impl LanguageRegistry { } }) .detach(); - Ok(server) + + Ok(Some(server)) }); return Some(PendingLanguageServer { @@ -944,19 +945,24 @@ impl LanguageRegistry { .clone(); drop(lock); - let binaries = entry.clone().map_err(|e| anyhow!(e)).await?; + let binary = match entry.clone().await.log_err() { + Some(binary) => binary, + None => return Ok(None), + }; if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { - task.await?; + if task.await.log_err().is_none() { + return Ok(None); + } } - lsp::LanguageServer::new( + Ok(Some(lsp::LanguageServer::new( server_id, - binaries, + binary, &root_path, adapter.code_action_kinds(), cx, - ) + )?)) }) }; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4c6b25b0e90fa3a1367d5e60e4a8133e069c6f64..7be57e921daf0378eebf7e661b3b4f48ee049620 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2471,7 +2471,7 @@ impl Project { .await; match result { - Ok(server) => Some(server), + Ok(server) => server, Err(err) => { log::error!("failed to start language server {:?}: {}", server_name, err); @@ -2571,8 +2571,8 @@ impl Project { server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), cx: &mut AsyncAppContext, - ) -> Result> { - let language_server = Self::setup_pending_language_server( + ) -> Result>> { + let setup = Self::setup_pending_language_server( this, initialization_options, pending_server, @@ -2580,8 +2580,12 @@ impl Project { languages, server_id, cx, - ) - .await?; + ); + + let language_server = match setup.await? { + Some(language_server) => language_server, + None => return Ok(None), + }; let this = match this.upgrade(cx) { Some(this) => this, @@ -2599,7 +2603,7 @@ impl Project { ) })?; - Ok(language_server) + Ok(Some(language_server)) } async fn setup_pending_language_server( @@ -2610,11 +2614,13 @@ impl Project { languages: Arc, server_id: LanguageServerId, cx: &mut AsyncAppContext, - ) -> Result> { + ) -> Result>> { let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = pending_server.task.await?; - let language_server = language_server.initialize(initialization_options).await?; + let language_server = match pending_server.task.await? { + Some(server) => server.initialize(initialization_options).await?, + None => return Ok(None), + }; language_server .on_notification::({ @@ -2756,7 +2762,7 @@ impl Project { ) .ok(); - Ok(language_server) + Ok(Some(language_server)) } fn insert_newly_running_language_server( From 5632f24d24f8a33177a32d67b22526d5cfac44b4 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 26 Jun 2023 10:18:30 -0400 Subject: [PATCH 018/169] Handle new elixir-ls release zip name --- crates/zed/src/languages/elixir.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index 4d3f08ff53f8fd9dff393218d1496f9a402141b3..afac5abeb023d5c87e37bca141ffa0b4f334bf65 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -62,16 +62,23 @@ impl LspAdapter for ElixirLspAdapter { &self, delegate: &dyn LspAdapterDelegate, ) -> Result> { - let release = - latest_github_release("elixir-lsp/elixir-ls", false, delegate.http_client()).await?; - let asset_name = "elixir-ls.zip"; + let http = delegate.http_client(); + let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?; + let version_name = release + .name + .strip_prefix("Release ") + .context("Elixir-ls release name does not start with prefix")? + .to_owned(); + + let asset_name = format!("elixir-ls-{}.zip", &version_name); let asset = release .assets .iter() .find(|asset| asset.name == asset_name) .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?; + let version = GitHubLspBinaryVersion { - name: release.name, + name: version_name, url: asset.browser_download_url.clone(), }; Ok(Box::new(version) as Box<_>) @@ -116,7 +123,7 @@ impl LspAdapter for ElixirLspAdapter { .await? .status; if !unzip_status.success() { - Err(anyhow!("failed to unzip clangd archive"))?; + Err(anyhow!("failed to unzip elixir-ls archive"))?; } remove_matching(&container_dir, |entry| entry != version_dir).await; From 2a8d1343d66b9a10558402323c6ea33f01ed6b3f Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 26 Jun 2023 11:54:20 -0400 Subject: [PATCH 019/169] Add installation test binaries for all remaining adapters --- crates/language/src/language.rs | 18 ++++- crates/project/src/project.rs | 9 +++ crates/zed/src/languages/elixir.rs | 39 ++++++---- crates/zed/src/languages/go.rs | 66 ++++++++++------ crates/zed/src/languages/html.rs | 72 +++++++++++------- crates/zed/src/languages/json.rs | 64 ++++++++++------ crates/zed/src/languages/lua.rs | 66 ++++++++++------ crates/zed/src/languages/python.rs | 70 ++++++++++------- crates/zed/src/languages/ruby.rs | 8 ++ crates/zed/src/languages/rust.rs | 39 +++++++--- crates/zed/src/languages/typescript.rs | 101 ++++++++++++++++--------- crates/zed/src/languages/yaml.rs | 69 ++++++++++------- 12 files changed, 395 insertions(+), 226 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4f03da1afe7df3d81a53a1c6802eabf49c6bac05..e8450344b8a627994cbe173a52c900339aa93cf3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -160,6 +160,10 @@ impl CachedLspAdapter { .await } + pub fn can_be_reinstalled(&self) -> bool { + self.adapter.can_be_reinstalled() + } + pub async fn installation_test_binary( &self, container_dir: PathBuf, @@ -249,12 +253,14 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; + fn can_be_reinstalled(&self) -> bool { + true + } + async fn installation_test_binary( &self, - _container_dir: PathBuf, - ) -> Option { - unimplemented!(); - } + container_dir: PathBuf, + ) -> Option; async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} @@ -1667,6 +1673,10 @@ impl LspAdapter for Arc { unreachable!(); } + async fn installation_test_binary(&self, _: PathBuf) -> Option { + unreachable!(); + } + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn disk_based_diagnostic_sources(&self) -> Vec { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7be57e921daf0378eebf7e661b3b4f48ee049620..0820eaf26e00d9f580254edc9115ee14299ae118 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3057,6 +3057,14 @@ impl Project { installation_test_binary: Option, cx: &mut ModelContext, ) { + if !adapter.can_be_reinstalled() { + log::info!( + "Validation check requested for {:?} but it cannot be reinstalled", + adapter.name.0 + ); + return; + } + cx.spawn(|this, mut cx| async move { log::info!("About to spawn test binary"); @@ -3086,6 +3094,7 @@ impl Project { _ = timeout => { log::info!("test binary time-ed out, this counts as a success"); + _ = process.kill(); } } } else { diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index afac5abeb023d5c87e37bca141ffa0b4f334bf65..c32927e15cfc177fe8c4611f8b5de686ebaffed6 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -140,20 +140,14 @@ impl LspAdapter for ElixirLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } - last.map(|path| LanguageServerBinary { - path, - arguments: vec![], - }) - .ok_or_else(|| anyhow!("no cached binary")) - })() - .await - .log_err() + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir).await } async fn label_for_completion( @@ -239,3 +233,20 @@ impl LspAdapter for ElixirLspAdapter { }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.map(|path| LanguageServerBinary { + path, + arguments: vec![], + }) + .ok_or_else(|| anyhow!("no cached binary")) + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 9821dcd1eb60f96b35a1d63ccd88abe5c7e9d8d7..d7982f7bdb471d6b0a154d60094f6e206618f0b1 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -149,32 +149,19 @@ impl super::LspAdapter for GoLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_binary_path = 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_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name.starts_with("gopls_")) - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) } async fn label_for_completion( @@ -337,6 +324,35 @@ impl super::LspAdapter for GoLspAdapter { } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last_binary_path = 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_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name.starts_with("gopls_")) + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + })() + .await + .log_err() +} + fn adjust_runs( delta: usize, mut runs: Vec<(Range, HighlightId)>, diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index 5e6f956b648101dae5afb3328b018ad7be183234..ecc839fca6875e54f0a0bf4d0671c675123418ef 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -14,6 +14,9 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = + "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -23,9 +26,6 @@ pub struct HtmlLspAdapter { } impl HtmlLspAdapter { - const SERVER_PATH: &'static str = - "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; - pub fn new(node: Arc) -> Self { HtmlLspAdapter { node } } @@ -55,7 +55,7 @@ impl LspAdapter for HtmlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -77,31 +77,14 @@ impl LspAdapter for HtmlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| 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(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn initialization_options(&self) -> Option { @@ -110,3 +93,34 @@ impl LspAdapter for HtmlLspAdapter { })) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| 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(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index f40e35ebd7199dc8bda3157c73649dcf26588fb4..b7e4ab4ba7b32491bbbb8aa025cab543dde113af 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -83,32 +83,14 @@ impl LspAdapter for JsonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| 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()); - } - } + get_cached_server_binary(container_dir, &self.node).await + } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn initialization_options(&self) -> Option { @@ -161,6 +143,38 @@ impl LspAdapter for JsonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| 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(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + fn schema_file_match(path: &Path) -> &Path { path.strip_prefix(path.parent().unwrap().parent().unwrap()) .unwrap() diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 3daabb64d093516aa83d2d92d26d107600d6b908..7c5c7179d019ee9c8c90e069fec55f297ceda2d5 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -92,31 +92,47 @@ impl super::LspAdapter for LuaLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - async_iife!({ - let mut last_binary_path = 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_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name == "lua-language-server") - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--version".into()]; + binary + }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + async_iife!({ + let mut last_binary_path = 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_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name == "lua-language-server") + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index 5be05bea2e2b8948442df83099578fc7810166a6..41ad28ba862e38e04b52ec5e4e1b77e87b183200 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -13,6 +13,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -22,8 +24,6 @@ pub struct PythonLspAdapter { } impl PythonLspAdapter { - const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; - pub fn new(node: Arc) -> Self { PythonLspAdapter { node } } @@ -49,7 +49,7 @@ impl LspAdapter for PythonLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -68,31 +68,14 @@ impl LspAdapter for PythonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| 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(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn process_completion(&self, item: &mut lsp::CompletionItem) { @@ -171,6 +154,37 @@ impl LspAdapter for PythonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| 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(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::{ModelContext, TestAppContext}; diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index f88f4af5fd323539b9280e0b4f982fefab71dfc6..358441352a7090084c46e2f6fd1d06b2aed8c68c 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -39,6 +39,14 @@ impl LspAdapter for RubyLanguageServer { }) } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn label_for_completion( &self, item: &lsp::CompletionItem, diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 0a2c93f42244cdfab0dcf092d22b2fdf25957838..97549b00583d45f3b03a730d2a10f43dc9b2f978 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -79,20 +79,19 @@ impl LspAdapter for RustLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } + get_cached_server_binary(container_dir).await + } - anyhow::Ok(LanguageServerBinary { - path: last.ok_or_else(|| anyhow!("no cached binary"))?, - arguments: Default::default(), + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary }) - })() - .await - .log_err() } async fn disk_based_diagnostic_sources(&self) -> Vec { @@ -259,6 +258,22 @@ impl LspAdapter for RustLspAdapter { }) } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: Default::default(), + }) + })() + .await + .log_err() +} #[cfg(test)] mod tests { diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index c1df52c161869ee1c6c12e60c6a3b5dd83a5ae58..e6d1466731491376ac9f6f1278814a0e90b5f625 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -104,28 +104,14 @@ impl LspAdapter for TypeScriptLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let old_server_path = container_dir.join(Self::OLD_SERVER_PATH); - let new_server_path = container_dir.join(Self::NEW_SERVER_PATH); - if new_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&new_server_path), - }) - } else if old_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&old_server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - container_dir - )) - } - })() - .await - .log_err() + get_cached_ts_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_ts_server_binary(container_dir, &self.node).await } fn code_action_kinds(&self) -> Option> { @@ -173,6 +159,34 @@ impl LspAdapter for TypeScriptLspAdapter { } } +async fn get_cached_ts_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH); + let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH); + if new_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&new_server_path), + }) + } else if old_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&old_server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + container_dir + )) + } + })() + .await + .log_err() +} + pub struct EsLintLspAdapter { node: Arc, } @@ -268,21 +282,14 @@ impl LspAdapter for EsLintLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - // This is unfortunate but we don't know what the version is to build a path directly - let mut dir = fs::read_dir(&container_dir).await?; - let first = dir.next().await.ok_or(anyhow!("missing first file"))??; - if !first.file_type().await?.is_dir() { - return Err(anyhow!("First entry is not a directory")); - } + get_cached_eslint_server_binary(container_dir, &self.node).await + } - Ok(LanguageServerBinary { - path: first.path().join(Self::SERVER_PATH), - arguments: Default::default(), - }) - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_eslint_server_binary(container_dir, &self.node).await } async fn label_for_completion( @@ -298,6 +305,28 @@ impl LspAdapter for EsLintLspAdapter { } } +async fn get_cached_eslint_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + // This is unfortunate but we don't know what the version is to build a path directly + let mut dir = fs::read_dir(&container_dir).await?; + let first = dir.next().await.ok_or(anyhow!("missing first file"))??; + if !first.file_type().await?.is_dir() { + return Err(anyhow!("First entry is not a directory")); + } + let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH); + + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: eslint_server_binary_arguments(&server_path), + }) + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::TestAppContext; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index c03bcf405e13051361aebe4fdaa72558a2a8fb6d..b57c6f5699498706de0b1c91655fb15848b1a2c6 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -18,6 +18,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -27,8 +29,6 @@ pub struct YamlLspAdapter { } impl YamlLspAdapter { - const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; - pub fn new(node: Arc) -> Self { YamlLspAdapter { node } } @@ -58,7 +58,7 @@ impl LspAdapter for YamlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -77,33 +77,15 @@ impl LspAdapter for YamlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| 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(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await } + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await + } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { let tab_size = all_language_settings(None, cx) .language(Some("YAML")) @@ -121,3 +103,34 @@ impl LspAdapter for YamlLspAdapter { ) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| 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(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} From b2de28ccfc9b3c1428693e5e7a30e7e7df64d157 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 27 Jun 2023 14:16:01 -0400 Subject: [PATCH 020/169] Match original logic when determining server to request formatting --- crates/project/src/project.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0820eaf26e00d9f580254edc9115ee14299ae118..561235d35fca0572a36c945173f0425715c4a80a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3895,7 +3895,7 @@ impl Project { let formatting_provider = capabilities.document_formatting_provider.as_ref(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - let lsp_edits = if !matches!(formatting_provider, Some(OneOf::Left(false))) { + let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { language_server .request::(lsp::DocumentFormattingParams { text_document, @@ -3903,7 +3903,7 @@ impl Project { work_done_progress_params: Default::default(), }) .await? - } else if !matches!(range_formatting_provider, Some(OneOf::Left(false))) { + } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) { let buffer_start = lsp::Position::new(0, 0); let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); From 18dd3102bfbda57411153d215cae110ea8cd8617 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 28 Jun 2023 09:29:49 -0700 Subject: [PATCH 021/169] WIP: Add click out event to fix context menus --- crates/context_menu/src/context_menu.rs | 27 +++++++++++--------- crates/gpui/src/app/window.rs | 6 ++++- crates/gpui/src/scene/mouse_event.rs | 22 +++++++++++++++++ crates/gpui/src/scene/mouse_region.rs | 33 ++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index de78b51e9c071aadd28750c8f45b5d039c502a33..9bdf146da47e4f8e882cabbf0fd116b7f5676b7f 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -301,18 +301,23 @@ impl ContextMenu { cx: &mut ViewContext, ) { let mut items = items.into_iter().peekable(); - if items.peek().is_some() { - self.items = items.collect(); - self.anchor_position = anchor_position; - self.anchor_corner = anchor_corner; - self.visible = true; - self.show_count += 1; - if !cx.is_self_focused() { - self.previously_focused_view_id = cx.focused_view_id(); - } - cx.focus_self(); - } else { + dbg!(self.visible); + if (self.visible) { self.visible = false; + } else { + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !cx.is_self_focused() { + self.previously_focused_view_id = cx.focused_view_id(); + } + cx.focus_self(); + } else { + self.visible = false; + } } cx.notify(); } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index cffce6c3a6c646094f7247683def5d148c550138..cc6778c930c0fb7b8740738ca0c202be8a4d8fd2 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -9,7 +9,7 @@ use crate::{ }, scene::{ CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, MouseClickOut, }, text_layout::TextLayoutCache, util::post_inc, @@ -524,6 +524,10 @@ impl<'a> WindowContext<'a> { region: Default::default(), platform_event: e.clone(), })); + mouse_events.push(MouseEvent::ClickOut(MouseClickOut { + region: Default::default(), + platform_event: e.clone(), + })); } Event::MouseMoved( diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index cf0a08f33ed50ead5e5f4619cbb024603de780a9..a492da771b848574e29411e91d8876873ead4917 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -99,6 +99,20 @@ impl Deref for MouseClick { } } +#[derive(Debug, Default, Clone)] +pub struct MouseClickOut { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseClickOut { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + #[derive(Debug, Default, Clone)] pub struct MouseDownOut { pub region: RectF, @@ -150,6 +164,7 @@ pub enum MouseEvent { Down(MouseDown), Up(MouseUp), Click(MouseClick), + ClickOut(MouseClickOut), DownOut(MouseDownOut), UpOut(MouseUpOut), ScrollWheel(MouseScrollWheel), @@ -165,6 +180,7 @@ impl MouseEvent { MouseEvent::Down(r) => r.region = region, MouseEvent::Up(r) => r.region = region, MouseEvent::Click(r) => r.region = region, + MouseEvent::ClickOut(r) => r.region = region, MouseEvent::DownOut(r) => r.region = region, MouseEvent::UpOut(r) => r.region = region, MouseEvent::ScrollWheel(r) => r.region = region, @@ -182,6 +198,7 @@ impl MouseEvent { MouseEvent::Down(_) => true, MouseEvent::Up(_) => true, MouseEvent::Click(_) => true, + MouseEvent::ClickOut(_) => true, MouseEvent::DownOut(_) => false, MouseEvent::UpOut(_) => false, MouseEvent::ScrollWheel(_) => true, @@ -222,6 +239,10 @@ impl MouseEvent { discriminant(&MouseEvent::Click(Default::default())) } + pub fn click_out_disc() -> Discriminant { + discriminant(&MouseEvent::ClickOut(Default::default())) + } + pub fn down_out_disc() -> Discriminant { discriminant(&MouseEvent::DownOut(Default::default())) } @@ -239,6 +260,7 @@ impl MouseEvent { MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), MouseEvent::Up(e) => HandlerKey::new(Self::up_disc(), Some(e.button)), MouseEvent::Click(e) => HandlerKey::new(Self::click_disc(), Some(e.button)), + MouseEvent::ClickOut(e) => HandlerKey::new(Self::click_out_disc(), Some(e.button)), MouseEvent::UpOut(e) => HandlerKey::new(Self::up_out_disc(), Some(e.button)), MouseEvent::DownOut(e) => HandlerKey::new(Self::down_out_disc(), Some(e.button)), MouseEvent::ScrollWheel(_) => HandlerKey::new(Self::scroll_wheel_disc(), None), diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 0efc794148868c537eae37f4027ae8f23afa177c..7e1d5d6e1e4f12c0162607d3c4460fe37092a348 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -14,7 +14,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseMoveOut, MouseScrollWheel, + MouseMoveOut, MouseScrollWheel, MouseClickOut, }; #[derive(Clone)] @@ -89,6 +89,15 @@ impl MouseRegion { self } + pub fn on_click_out(mut self, button: MouseButton, handler: F) -> Self + where + V: View, + F: Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + { + self.handlers = self.handlers.on_click(button, handler); + self + } + pub fn on_down_out(mut self, button: MouseButton, handler: F) -> Self where V: View, @@ -405,6 +414,28 @@ impl HandlerSet { self } + pub fn on_click_out(mut self, button: MouseButton, handler: F) -> Self + where + V: View, + F: Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + { + self.insert(MouseEvent::click_out_disc(), Some(button), + Rc::new(move |region_event, view, cx, view_id| { + if let MouseEvent::ClickOut(e) = region_event { + let view = view.downcast_mut().unwrap(); + let mut cx = ViewContext::mutable(cx, view_id); + let mut cx = EventContext::new(&mut cx); + handler(e, view, &mut cx); + cx.handled + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ClickOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_down_out(mut self, button: MouseButton, handler: F) -> Self where V: View, From e30ad9109cc835a1866d63c8239dd10b751ebcd8 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 28 Jun 2023 16:28:46 -0400 Subject: [PATCH 022/169] wip --- styles/.eslintrc.js | 33 + styles/package-lock.json | 2961 ++++++++++++++++++++++++-- styles/package.json | 7 +- styles/src/styleTree/sharedScreen.ts | 3 +- styles/src/styleTree/terminal.ts | 63 +- styles/src/types/element.ts | 4 + styles/src/types/index.ts | 5 + styles/src/types/property.ts | 9 + styles/src/types/styleTree.ts | 29 + styles/src/types/util.ts | 15 + 10 files changed, 2974 insertions(+), 155 deletions(-) create mode 100644 styles/.eslintrc.js create mode 100644 styles/src/types/element.ts create mode 100644 styles/src/types/index.ts create mode 100644 styles/src/types/property.ts create mode 100644 styles/src/types/styleTree.ts create mode 100644 styles/src/types/util.ts diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..40800d315cb0d7968593d502984c8086e878222d --- /dev/null +++ b/styles/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + plugins: ["import"], + parser: "@typescript-eslint/parser", + parserOptions: { + sourceType: "module" + }, + "settings": { + "import/parsers": { + "@typescript-eslint/parser": [".ts"] + }, + "import/resolver": { + "typescript": { + "alwaysTryTypes": true, + } + } + }, + rules: { + "import/no-restricted-paths": [ + warn, + { + zones: [ + { + "target": "./src/types/*", + "from": "./src", + "except": [ + "./src/types/index.ts" + ] + } + ] + } + ] + } +} diff --git a/styles/package-lock.json b/styles/package-lock.json index 236e5711485b559ab7d2fc9f7c87017f68af5402..e5a761b2a2087e6ec22793a18168e4f0bebea640 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -25,7 +25,21 @@ "vitest": "^0.32.0" }, "devDependencies": { - "@vitest/coverage-v8": "^0.32.0" + "@typescript-eslint/parser": "^5.60.1", + "@vitest/coverage-v8": "^0.32.0", + "eslint": "^8.43.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "typescript": "^5.1.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@ampproject/remapping": { @@ -90,6 +104,95 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -149,6 +252,67 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.2.12", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true + }, "node_modules/@tokens-studio/types": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz", @@ -212,6 +376,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.195", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", @@ -232,6 +402,107 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@typescript-eslint/parser": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/coverage-v8": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.32.0.tgz", @@ -332,6 +603,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -340,6 +620,22 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -374,6 +670,83 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -382,6 +755,18 @@ "node": "*" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ayu": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ayu/-/ayu-8.0.1.tgz", @@ -402,11 +787,32 @@ "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" }, + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -416,6 +822,33 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -424,11 +857,33 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", @@ -457,6 +912,37 @@ "node": ">=4" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -485,14 +971,32 @@ "node": ">=0.10" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concordance": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", "dependencies": { "date-time": "^3.1.0", @@ -519,6 +1023,20 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -566,6 +1084,12 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", @@ -574,6 +1098,68 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -582,6 +1168,131 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -662,143 +1373,996 @@ "@esbuild/win32-x64": "0.17.19" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "node_modules/eslint": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "dev": true, "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, "dependencies": { - "type": "^2.7.2" + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz", + "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "get-tsconfig": "^4.5.0", + "globby": "^13.1.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.5" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" } }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz", + "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, "engines": { - "node": "*" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "debug": "^3.2.7" }, "engines": { - "node": "*" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/glob-promise": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", - "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { - "@types/glob": "^7.1.3" + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/ahmadnassri" + "node": ">=4" }, "peerDependencies": { - "glob": "^7.1.6" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", + "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", + "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==", + "dependencies": { + "@types/glob": "^7.1.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/ahmadnassri" + }, + "peerDependencies": { + "glob": "^7.1.6" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/is-extglob": { "version": "2.1.1", @@ -819,11 +2383,208 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -920,11 +2681,48 @@ "node": ">=12.0.0" } }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/local-pkg": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", @@ -936,11 +2734,32 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -1034,6 +2853,46 @@ "timers-ext": "^0.1.7" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1107,6 +2966,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -1117,6 +2982,33 @@ "resolved": "https://registry.npmjs.org/nonenumerable/-/nonenumerable-1.1.1.tgz", "integrity": "sha512-ptUD9w9D8WqW6fuJJkZNCImkf+0vdbgUTbRK3i7jsy3olqtH96hYE6Q/S3Tx9NWbcB/ocAjYshXCAUP0lZ9B4Q==" }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1125,6 +3017,59 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1133,6 +3078,56 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -1147,6 +3142,69 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1155,6 +3213,30 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", @@ -1173,6 +3255,18 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -1210,6 +3304,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -1234,27 +3337,274 @@ "react-is": "^17.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", + "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "node_modules/rollup": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", - "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", - "bin": { - "rollup": "dist/bin/rollup" - }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { @@ -1271,11 +3621,61 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1303,6 +3703,96 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==" }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.1.tgz", @@ -1326,6 +3816,49 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -1340,6 +3873,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -1397,6 +3936,30 @@ "node": ">=14.0.0" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toml": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", @@ -1452,11 +4015,56 @@ } } }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -1465,17 +4073,42 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", + "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ufo": { @@ -1483,6 +4116,39 @@ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz", "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==" }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utility-types": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", @@ -1674,6 +4340,57 @@ "node": ">=6" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", diff --git a/styles/package.json b/styles/package.json index b14fb5f527ee53ffcf37b68ae97f65abc2b72faa..19a77d4dbf8c6441e00e9d0198d7e3bac4602a5e 100644 --- a/styles/package.json +++ b/styles/package.json @@ -35,6 +35,11 @@ "tabWidth": 4 }, "devDependencies": { - "@vitest/coverage-v8": "^0.32.0" + "@typescript-eslint/parser": "^5.60.1", + "@vitest/coverage-v8": "^0.32.0", + "eslint": "^8.43.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "typescript": "^5.1.5" } } diff --git a/styles/src/styleTree/sharedScreen.ts b/styles/src/styleTree/sharedScreen.ts index 2563e718ff2a6d3013f4bc235901aa06cb32b4ba..a58e7e022290ec9bc0f3b79aea1d17f50fd9fd62 100644 --- a/styles/src/styleTree/sharedScreen.ts +++ b/styles/src/styleTree/sharedScreen.ts @@ -1,7 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" +import { StyleTree } from "../types" import { background } from "./components" -export default function sharedScreen(colorScheme: ColorScheme) { +export default function sharedScreen(colorScheme: ColorScheme): StyleTree.SharedScreen { let layer = colorScheme.highest return { background: background(layer), diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts index 8a7eb7a549a18acbb17b115c8306e245e7a8dec3..5198cb195dd30b78130c917e09798392ca56a393 100644 --- a/styles/src/styleTree/terminal.ts +++ b/styles/src/styleTree/terminal.ts @@ -1,6 +1,7 @@ import { ColorScheme } from "../theme/colorScheme" +import { StyleTree } from "../types" -export default function terminal(colorScheme: ColorScheme) { +export default function terminal(theme: ColorScheme): StyleTree.TerminalStyle { /** * Colors are controlled per-cell in the terminal grid. * Cells can be set to any of these more 'theme-capable' colors @@ -9,44 +10,44 @@ export default function terminal(colorScheme: ColorScheme) { * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */ return { - black: colorScheme.ramps.neutral(0).hex(), - red: colorScheme.ramps.red(0.5).hex(), - green: colorScheme.ramps.green(0.5).hex(), - yellow: colorScheme.ramps.yellow(0.5).hex(), - blue: colorScheme.ramps.blue(0.5).hex(), - magenta: colorScheme.ramps.magenta(0.5).hex(), - cyan: colorScheme.ramps.cyan(0.5).hex(), - white: colorScheme.ramps.neutral(1).hex(), - brightBlack: colorScheme.ramps.neutral(0.4).hex(), - brightRed: colorScheme.ramps.red(0.25).hex(), - brightGreen: colorScheme.ramps.green(0.25).hex(), - brightYellow: colorScheme.ramps.yellow(0.25).hex(), - brightBlue: colorScheme.ramps.blue(0.25).hex(), - brightMagenta: colorScheme.ramps.magenta(0.25).hex(), - brightCyan: colorScheme.ramps.cyan(0.25).hex(), - brightWhite: colorScheme.ramps.neutral(1).hex(), + black: theme.ramps.neutral(0).hex(), + red: theme.ramps.red(0.5).hex(), + green: theme.ramps.green(0.5).hex(), + yellow: theme.ramps.yellow(0.5).hex(), + blue: theme.ramps.blue(0.5).hex(), + magenta: theme.ramps.magenta(0.5).hex(), + cyan: theme.ramps.cyan(0.5).hex(), + white: theme.ramps.neutral(1).hex(), + bright_black: theme.ramps.neutral(0.4).hex(), + bright_red: theme.ramps.red(0.25).hex(), + bright_green: theme.ramps.green(0.25).hex(), + bright_yellow: theme.ramps.yellow(0.25).hex(), + bright_blue: theme.ramps.blue(0.25).hex(), + bright_magenta: theme.ramps.magenta(0.25).hex(), + bright_cyan: theme.ramps.cyan(0.25).hex(), + bright_white: theme.ramps.neutral(1).hex(), /** * Default color for characters */ - foreground: colorScheme.ramps.neutral(1).hex(), + foreground: theme.ramps.neutral(1).hex(), /** * Default color for the rectangle background of a cell */ - background: colorScheme.ramps.neutral(0).hex(), - modalBackground: colorScheme.ramps.neutral(0.1).hex(), + background: theme.ramps.neutral(0).hex(), + modal_background: theme.ramps.neutral(0.1).hex(), /** * Default color for the cursor */ - cursor: colorScheme.players[0].cursor, - dimBlack: colorScheme.ramps.neutral(1).hex(), - dimRed: colorScheme.ramps.red(0.75).hex(), - dimGreen: colorScheme.ramps.green(0.75).hex(), - dimYellow: colorScheme.ramps.yellow(0.75).hex(), - dimBlue: colorScheme.ramps.blue(0.75).hex(), - dimMagenta: colorScheme.ramps.magenta(0.75).hex(), - dimCyan: colorScheme.ramps.cyan(0.75).hex(), - dimWhite: colorScheme.ramps.neutral(0.6).hex(), - brightForeground: colorScheme.ramps.neutral(1).hex(), - dimForeground: colorScheme.ramps.neutral(0).hex(), + cursor: theme.players[0].cursor, + dim_black: theme.ramps.neutral(1).hex(), + dim_red: theme.ramps.red(0.75).hex(), + dim_green: theme.ramps.green(0.75).hex(), + dim_yellow: theme.ramps.yellow(0.75).hex(), + dim_blue: theme.ramps.blue(0.75).hex(), + dim_magenta: theme.ramps.magenta(0.75).hex(), + dim_cyan: theme.ramps.cyan(0.75).hex(), + dim_white: theme.ramps.neutral(0.6).hex(), + bright_foreground: theme.ramps.neutral(1).hex(), + dim_foreground: theme.ramps.neutral(0).hex(), } } diff --git a/styles/src/types/element.ts b/styles/src/types/element.ts new file mode 100644 index 0000000000000000000000000000000000000000..719fe38203623dbf3d0e936d4fc9f5b474b1ac1b --- /dev/null +++ b/styles/src/types/element.ts @@ -0,0 +1,4 @@ +import { Clean } from "./util"; +import * as zed from './zed' + +export type Text = Clean diff --git a/styles/src/types/index.ts b/styles/src/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1f55a97e2228acb5e8eaa5e02a32f50beec905c --- /dev/null +++ b/styles/src/types/index.ts @@ -0,0 +1,5 @@ +import * as StyleTree from './styleTree' +import * as Property from './property' +import * as Element from './element' + +export { StyleTree, Property, Element } diff --git a/styles/src/types/property.ts b/styles/src/types/property.ts new file mode 100644 index 0000000000000000000000000000000000000000..97867c9858774b0672d23e3afca9523f84b691c5 --- /dev/null +++ b/styles/src/types/property.ts @@ -0,0 +1,9 @@ +import { Clean } from './util' +import * as zed from './zed' + +export type Color = zed.Color +export type CursorStyle = zed.CursorStyle +export type FontStyle = zed.Style +export type Border = Clean +export type Margin = Clean +export type Padding = Clean diff --git a/styles/src/types/styleTree.ts b/styles/src/types/styleTree.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4194ca12d2ff33dec391db443f5bcdc33be577e --- /dev/null +++ b/styles/src/types/styleTree.ts @@ -0,0 +1,29 @@ +import { Clean } from './util' +import * as zed from './zed' + +export type AssistantStyle = Readonly>; +export type CommandPalette = Readonly>; +export type ContactFinder = Readonly>; +export type ContactList = Readonly>; +export type ContactNotification = Readonly>; +export type ContactsPopover = Readonly>; +export type ContextMenu = Readonly>; +export type Copilot = Readonly>; +export type Editor = Readonly>; +export type FeedbackStyle = Readonly>; +export type IncomingCallNotification = Readonly>; +export type ThemeMeta = Readonly>; +export type Picker = Readonly>; +export type ProjectDiagnostics = Readonly>; +export type ProjectPanel = Readonly>; +export type ProjectSharedNotification = Readonly>; +export type Search = Readonly>; +export type SharedScreen = Readonly>; +export type MessageNotification = Readonly>; +export type TerminalStyle = Readonly>; +export type UserMenu = Readonly>; +export type DropdownMenu = Readonly>; +export type TooltipStyle = Readonly>; +export type UpdateNotification = Readonly>; +export type WelcomeStyle = Readonly>; +export type Workspace = Readonly>; diff --git a/styles/src/types/util.ts b/styles/src/types/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..3fa2f0eb25637fcaa796ad91436adb9802427863 --- /dev/null +++ b/styles/src/types/util.ts @@ -0,0 +1,15 @@ +export type Prettify = { + [K in keyof T]: T[K]; +} & {}; + +/** +* Clean removes the [k: string]: unknown property from an object, +* and Prettifies it, providing better hover information for the type +*/ +export type Clean = { + [K in keyof T as string extends K ? never : K]: T[K]; +} + +export type DeepClean = { + [K in keyof T as string extends K ? never : K]: T[K] extends object ? DeepClean : T[K]; +} From db2b3e47bc91dfba556ed2c9af24702f7dc2bcbe Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 28 Jun 2023 16:43:45 -0400 Subject: [PATCH 023/169] Reinstall Node whenever a NodeRuntime operation has serious error --- Cargo.lock | 1 + crates/copilot/src/copilot.rs | 2 +- crates/node_runtime/Cargo.toml | 1 + crates/node_runtime/src/node_runtime.rs | 203 ++++++++++++++---------- crates/zed/src/languages/typescript.rs | 4 +- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 2 +- 7 files changed, 127 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4b12223e5a8fe6770464280bf652e08346a26ba..55e10ed32620816839622831db11168f2a11c004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4138,6 +4138,7 @@ dependencies = [ "async-tar", "futures 0.3.28", "gpui", + "log", "parking_lot 0.11.2", "serde", "serde_derive", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 4c45bf823bf8f77db9fbcea95e731e37475f4562..ce4938ed0d9e31c8130625b526095aeb0283e12c 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -340,7 +340,7 @@ impl Copilot { let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let this = cx.add_model(|cx| Self { http: http.clone(), - node_runtime: NodeRuntime::new(http, cx.background().clone()), + node_runtime: NodeRuntime::instance(http, cx.background().clone()), server: CopilotServer::Running(RunningCopilotServer { lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index fce0fdfe506201e6d126c141c0b692bd7ff39ada..53635f2725ba7a982d773abfd642104bca57d10f 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -20,3 +20,4 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smol.workspace = true +log.workspace = true diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index e2a8d0d0032e23ea6751855acce33d90bded46d8..27a763e7f8851722907749c1a28a958827c3f9f5 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -1,21 +1,24 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; +use futures::lock::Mutex; use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; -use parking_lot::Mutex; use serde::Deserialize; use smol::{fs, io::BufReader, process::Command}; +use std::process::Output; use std::{ env::consts, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, OnceLock}, }; -use util::http::HttpClient; +use util::{http::HttpClient, ResultExt}; const VERSION: &str = "v18.15.0"; -#[derive(Deserialize)] +static RUNTIME_INSTANCE: OnceLock> = OnceLock::new(); + +#[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NpmInfo { #[serde(default)] @@ -23,7 +26,7 @@ pub struct NpmInfo { versions: Vec, } -#[derive(Deserialize, Default)] +#[derive(Debug, Deserialize, Default)] pub struct NpmInfoDistTags { latest: Option, } @@ -35,12 +38,16 @@ pub struct NodeRuntime { } impl NodeRuntime { - pub fn new(http: Arc, background: Arc) -> Arc { - Arc::new(NodeRuntime { - http, - background, - installation_path: Mutex::new(None), - }) + pub fn instance(http: Arc, background: Arc) -> Arc { + RUNTIME_INSTANCE + .get_or_init(|| { + Arc::new(NodeRuntime { + http, + background, + installation_path: Mutex::new(None), + }) + }) + .clone() } pub async fn binary_path(&self) -> Result { @@ -50,55 +57,74 @@ impl NodeRuntime { pub async fn run_npm_subcommand( &self, - directory: &Path, + directory: Option<&Path>, subcommand: &str, args: &[&str], - ) -> Result<()> { + ) -> Result { + let attempt = |installation_path: PathBuf| async move { + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + if smol::fs::metadata(&node_binary).await.is_err() { + return Err(anyhow!("missing node binary file")); + } + + if smol::fs::metadata(&npm_file).await.is_err() { + return Err(anyhow!("missing npm file")); + } + + let mut command = Command::new(node_binary); + command.arg(npm_file).arg(subcommand).args(args); + + if let Some(directory) = directory { + command.current_dir(directory); + } + + command.output().await.map_err(|e| anyhow!("{e}")) + }; + let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .arg(subcommand) - .args(args) - .current_dir(directory) - .output() - .await?; + let mut output = attempt(installation_path).await; + if output.is_err() { + let installation_path = self.reinstall().await?; + output = attempt(installation_path).await; + if output.is_err() { + return Err(anyhow!( + "failed to launch npm subcommand {subcommand} subcommand" + )); + } + } - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); + if let Ok(output) = &output { + if !output.status.success() { + return Err(anyhow!( + "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } } - Ok(()) + output.map_err(|e| anyhow!("{e}")) } pub async fn npm_package_latest_version(&self, name: &str) -> Result { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .args(["info", name, "--json"]) - .output() - .await - .context("failed to run npm info")?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + let output = self + .run_npm_subcommand( + None, + "info", + &[ + name, + "--json", + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ], + ) + .await?; let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; info.dist_tags @@ -112,41 +138,54 @@ impl NodeRuntime { directory: &Path, packages: impl IntoIterator, ) -> Result<()> { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .arg("install") - .arg("--prefix") - .arg(directory) - .args( - packages - .into_iter() - .map(|(name, version)| format!("{name}@{version}")), - ) - .output() - .await - .context("failed to run npm install")?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + let packages: Vec<_> = packages + .into_iter() + .map(|(name, version)| format!("{name}@{version}")) + .collect(); + + let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); + arguments.extend_from_slice(&[ + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ]); + + self.run_npm_subcommand(Some(directory), "install", &arguments) + .await?; Ok(()) } + async fn reinstall(&self) -> Result { + log::info!("beginnning to reinstall Node runtime"); + let mut installation_path = self.installation_path.lock().await; + + if let Some(task) = installation_path.as_ref().cloned() { + if let Ok(installation_path) = task.await { + smol::fs::remove_dir_all(&installation_path) + .await + .context("node dir removal") + .log_err(); + } + } + + let http = self.http.clone(); + let task = self + .background + .spawn(async move { Self::install(http).await.map_err(Arc::new) }) + .shared(); + + *installation_path = Some(task.clone()); + task.await.map_err(|e| anyhow!("{}", e)) + } + async fn install_if_needed(&self) -> Result { let task = self .installation_path .lock() + .await .get_or_insert_with(|| { let http = self.http.clone(); self.background @@ -155,13 +194,11 @@ impl NodeRuntime { }) .clone(); - match task.await { - Ok(path) => Ok(path), - Err(error) => Err(anyhow!("{}", error)), - } + task.await.map_err(|e| anyhow!("{}", e)) } async fn install(http: Arc) -> Result { + log::info!("installing Node runtime"); let arch = match consts::ARCH { "x86_64" => "x64", "aarch64" => "arm64", diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index e6d1466731491376ac9f6f1278814a0e90b5f625..0a47d365b598aa41df1c1fa50aedd7d718aceb87 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -263,11 +263,11 @@ impl LspAdapter for EsLintLspAdapter { fs::rename(first.path(), &repo_root).await?; self.node - .run_npm_subcommand(&repo_root, "install", &[]) + .run_npm_subcommand(Some(&repo_root), "install", &[]) .await?; self.node - .run_npm_subcommand(&repo_root, "run-script", &["compile"]) + .run_npm_subcommand(Some(&repo_root), "run-script", &["compile"]) .await?; } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index dcdf5c1ea5f542339cfac3ef7edeac61c5ab34c4..6421014a4d1eb742030f7434177deed65164a122 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -131,7 +131,7 @@ fn main() { languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http.clone(), cx.background().to_owned()); languages::init(languages.clone(), node_runtime.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bcdfe57a469013251994b4112500daa4a7489027..260c7edf94a55647a30636eb872daa3128de19ce 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2177,7 +2177,7 @@ mod tests { languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let http = FakeHttpClient::with_404_response(); - let node_runtime = NodeRuntime::new(http, cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http, cx.background().to_owned()); languages::init(languages.clone(), node_runtime); for name in languages.language_names() { languages.language_for_name(&name); From 11779801728074a514b3150c091ced8028779a74 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 28 Jun 2023 16:44:18 -0400 Subject: [PATCH 024/169] Fix basic eslint errors --- styles/.eslintrc.js | 39 ++++++++-- styles/package-lock.json | 128 ++++++++++++++++++++++++++++++++ styles/package.json | 1 + styles/src/buildThemes.ts | 10 +-- styles/src/styleTree/search.ts | 4 +- styles/src/styleTree/welcome.ts | 6 +- styles/src/types/element.ts | 2 +- styles/src/types/util.ts | 2 +- 8 files changed, 173 insertions(+), 19 deletions(-) diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js index 40800d315cb0d7968593d502984c8086e878222d..4cb655c9daa2d2b800ab0b91318e599d240bbf4c 100644 --- a/styles/.eslintrc.js +++ b/styles/.eslintrc.js @@ -1,8 +1,21 @@ module.exports = { - plugins: ["import"], - parser: "@typescript-eslint/parser", - parserOptions: { - sourceType: "module" + 'env': { + "node": true + }, + 'extends': [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended' + ], + 'parser': '@typescript-eslint/parser', + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module' + }, + 'plugins': [ + '@typescript-eslint', 'import' + ], + globals: { + module: true }, "settings": { "import/parsers": { @@ -14,11 +27,23 @@ module.exports = { } } }, - rules: { + 'rules': { + 'indent': [ + 'error', + 4 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'semi': [ + 'error', + 'never' + ], "import/no-restricted-paths": [ - warn, + 'error', { - zones: [ + 'zones': [ { "target": "./src/types/*", "from": "./src", diff --git a/styles/package-lock.json b/styles/package-lock.json index e5a761b2a2087e6ec22793a18168e4f0bebea640..b6f14480f53188e0db903f0792b41617be6642be 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -25,6 +25,7 @@ "vitest": "^0.32.0" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", "@vitest/coverage-v8": "^0.32.0", "eslint": "^8.43.0", @@ -402,6 +403,46 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", + "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/type-utils": "5.60.1", + "@typescript-eslint/utils": "5.60.1", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/parser": { "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", @@ -446,6 +487,33 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", + "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.60.1", + "@typescript-eslint/utils": "5.60.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/types": { "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", @@ -486,6 +554,54 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", @@ -2104,6 +2220,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2972,6 +3094,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", diff --git a/styles/package.json b/styles/package.json index 19a77d4dbf8c6441e00e9d0198d7e3bac4602a5e..e21840e613c6769be51a46ba9381d6460df9fe56 100644 --- a/styles/package.json +++ b/styles/package.json @@ -35,6 +35,7 @@ "tabWidth": 4 }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", "@vitest/coverage-v8": "^0.32.0", "eslint": "^8.43.0", diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 8d807d62f3f597457cbff1e0544390d4ef37bb71..7d831ee42ed74f7e6754cba41ab4801fa51f206f 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -24,11 +24,11 @@ function clearThemes(themeDirectory: string) { function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { clearThemes(outputDirectory) - for (let colorScheme of colorSchemes) { - let styleTree = snakeCase(app(colorScheme)) - let styleTreeJSON = JSON.stringify(styleTree, null, 2) - let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`) - let outPath = path.join(outputDirectory, `${colorScheme.name}.json`) + for (const colorScheme of colorSchemes) { + const styleTree = snakeCase(app(colorScheme)) + const styleTreeJSON = JSON.stringify(styleTree, null, 2) + const tempPath = path.join(tempDirectory, `${colorScheme.name}.json`) + const outPath = path.join(outputDirectory, `${colorScheme.name}.json`) fs.writeFileSync(tempPath, styleTreeJSON) fs.renameSync(tempPath, outPath) console.log(`- ${outPath} created`) diff --git a/styles/src/styleTree/search.ts b/styles/src/styleTree/search.ts index b471e6cbdab31249acd93bd13fda824f4343fa13..9a86d1d558db1c6a7d54e86e693c4bac8f076aea 100644 --- a/styles/src/styleTree/search.ts +++ b/styles/src/styleTree/search.ts @@ -3,8 +3,8 @@ import { withOpacity } from "../theme/color" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function search(colorScheme: ColorScheme) { - let layer = colorScheme.highest +export default function search(colorScheme: ColorScheme): unknown { + const layer = colorScheme.highest // Search input const editor = { diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 311ff6daffa5ce468652ef0eb53dfa3d74c32eb2..3b3eeba53ad273862d09427b88ac999ee994f8c1 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -11,9 +11,9 @@ import { import { interactive } from "../element" export default function welcome(colorScheme: ColorScheme) { - let layer = colorScheme.highest + const layer = colorScheme.highest - let checkboxBase = { + const checkboxBase = { cornerRadius: 4, padding: { left: 3, @@ -30,7 +30,7 @@ export default function welcome(colorScheme: ColorScheme) { }, } - let interactive_text_size: TextProperties = { size: "sm" } + const interactive_text_size: TextProperties = { size: "sm" } return { pageWidth: 320, diff --git a/styles/src/types/element.ts b/styles/src/types/element.ts index 719fe38203623dbf3d0e936d4fc9f5b474b1ac1b..6f6bb91e58df016b0a46266ac73ac7b0d84743f5 100644 --- a/styles/src/types/element.ts +++ b/styles/src/types/element.ts @@ -1,4 +1,4 @@ -import { Clean } from "./util"; +import { Clean } from "./util" import * as zed from './zed' export type Text = Clean diff --git a/styles/src/types/util.ts b/styles/src/types/util.ts index 3fa2f0eb25637fcaa796ad91436adb9802427863..851acd4b180217a7b7da5fb1102b528d4440c73d 100644 --- a/styles/src/types/util.ts +++ b/styles/src/types/util.ts @@ -1,6 +1,6 @@ export type Prettify = { [K in keyof T]: T[K]; -} & {}; +} & unknown; /** * Clean removes the [k: string]: unknown property from an object, From 2ed0284d4979e42eeadd535bb1462de53d3d3401 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 28 Jun 2023 17:06:50 -0400 Subject: [PATCH 025/169] Stub out for language plugin --- crates/zed/src/languages/language_plugin.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 11720d899308227a20b252f3371991322b0fc17d..b0719363929232aa47721624a63a9b8bb4aacb5d 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -130,6 +130,14 @@ impl LspAdapter for PluginLspAdapter { .await } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn initialization_options(&self) -> Option { let string: String = self .runtime From bfdd0824e210654e8bbc909eb98db0b477fbe0c1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 28 Jun 2023 17:54:36 -0400 Subject: [PATCH 026/169] Resolve TS errors and warnings TODO: Use StyleTree types to remove `any`s from styleTrees. --- styles/.eslintrc.js | 87 ++-- styles/src/buildLicenses.ts | 4 +- styles/src/buildTypes.ts | 27 +- styles/src/element/interactive.ts | 4 +- styles/src/styleTree/app.ts | 2 +- styles/src/styleTree/assistant.ts | 2 +- styles/src/styleTree/commandPalette.ts | 4 +- styles/src/styleTree/components.ts | 14 +- styles/src/styleTree/contactFinder.ts | 2 +- styles/src/styleTree/contactList.ts | 4 +- styles/src/styleTree/contactNotification.ts | 4 +- styles/src/styleTree/contactsPopover.ts | 7 +- styles/src/styleTree/contextMenu.ts | 4 +- styles/src/styleTree/copilot.ts | 8 +- styles/src/styleTree/editor.ts | 4 +- styles/src/styleTree/feedback.ts | 4 +- styles/src/styleTree/hoverPopover.ts | 6 +- .../src/styleTree/incomingCallNotification.ts | 4 +- styles/src/styleTree/picker.ts | 2 +- styles/src/styleTree/projectDiagnostics.ts | 4 +- styles/src/styleTree/projectPanel.ts | 4 +- .../styleTree/projectSharedNotification.ts | 4 +- styles/src/styleTree/search.ts | 2 +- styles/src/styleTree/sharedScreen.ts | 6 +- .../styleTree/simpleMessageNotification.ts | 4 +- styles/src/styleTree/statusBar.ts | 4 +- styles/src/styleTree/tabBar.ts | 6 +- styles/src/styleTree/titlebar.ts | 2 +- styles/src/styleTree/toggle.ts | 47 -- styles/src/styleTree/toolbarDropdownMenu.ts | 4 +- styles/src/styleTree/tooltip.ts | 4 +- styles/src/styleTree/updateNotification.ts | 4 +- styles/src/styleTree/welcome.ts | 2 +- styles/src/styleTree/workspace.ts | 2 +- styles/src/system/lib/convert.ts | 11 - styles/src/system/lib/curve.ts | 26 - styles/src/system/lib/generate.ts | 159 ------- styles/src/system/ref/color.ts | 445 ------------------ styles/src/system/ref/curves.ts | 25 - styles/src/system/system.ts | 32 -- styles/src/system/types.ts | 66 --- styles/src/theme/colorScheme.ts | 6 +- styles/src/theme/ramps.ts | 12 +- styles/src/theme/syntax.ts | 2 +- styles/src/themes/gruvbox/gruvbox-common.ts | 23 +- styles/src/types/element.ts | 2 +- styles/src/types/index.ts | 6 +- styles/src/types/property.ts | 4 +- styles/src/types/styleTree.ts | 60 +-- styles/src/types/util.ts | 16 +- styles/src/utils/slugify.ts | 4 +- styles/src/utils/snakeCase.ts | 7 +- styles/tsconfig.json | 36 +- 53 files changed, 224 insertions(+), 1010 deletions(-) delete mode 100644 styles/src/styleTree/toggle.ts delete mode 100644 styles/src/system/lib/convert.ts delete mode 100644 styles/src/system/lib/curve.ts delete mode 100644 styles/src/system/lib/generate.ts delete mode 100644 styles/src/system/ref/color.ts delete mode 100644 styles/src/system/ref/curves.ts delete mode 100644 styles/src/system/system.ts delete mode 100644 styles/src/system/types.ts diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js index 4cb655c9daa2d2b800ab0b91318e599d240bbf4c..c131089e141419914f41bb5c1f623d554ddf95c1 100644 --- a/styles/.eslintrc.js +++ b/styles/.eslintrc.js @@ -1,58 +1,57 @@ module.exports = { - 'env': { - "node": true + env: { + node: true, }, - 'extends': [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended' + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/typescript", ], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module' + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", }, - 'plugins': [ - '@typescript-eslint', 'import' - ], + plugins: ["@typescript-eslint", "import"], globals: { - module: true + module: true, }, - "settings": { + settings: { "import/parsers": { - "@typescript-eslint/parser": [".ts"] + "@typescript-eslint/parser": [".ts"], }, "import/resolver": { - "typescript": { - "alwaysTryTypes": true, - } - } + typescript: true, + node: true, + }, + "import/extensions": [".ts"], }, - 'rules': { - 'indent': [ - 'error', - 4 - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'semi': [ - 'error', - 'never' - ], + rules: { + "linebreak-style": ["error", "unix"], + semi: ["error", "never"], "import/no-restricted-paths": [ - 'error', + "error", { - 'zones': [ + zones: [ { - "target": "./src/types/*", - "from": "./src", - "except": [ - "./src/types/index.ts" - ] - } - ] - } - ] - } + target: [ + "./src/component/*", + "./src/element/*", + "./src/styleTree/*", + "./src/system/*", + "./src/theme/*", + "./src/themes/*", + "./src/utils/*", + ], + from: [ + "./src/types/styleTree.ts", + "./src/types/element.ts", + "./src/types/property.ts", + ], + message: "Import from `@types` instead", + }, + ], + }, + ], + }, } diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index 13a6951a8288bfd2349dc466364bb4df517f08cf..93a2bd302aa1d88cbd2050b681c8fe110a3bef1f 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -7,9 +7,9 @@ const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses. // Use the cargo-about configuration file as the source of truth for supported licenses. function parseAcceptedToml(file: string): string[] { - let buffer = fs.readFileSync(file).toString() + const buffer = fs.readFileSync(file).toString() - let obj = toml.parse(buffer) + const obj = toml.parse(buffer) if (!Array.isArray(obj.accepted)) { throw Error("Accepted license source is malformed") diff --git a/styles/src/buildTypes.ts b/styles/src/buildTypes.ts index 8c1981cf97c147803bc3d01f9d359bba714ecf51..cd788c540ed0172cce7fb07ac538919fc8431187 100644 --- a/styles/src/buildTypes.ts +++ b/styles/src/buildTypes.ts @@ -9,22 +9,22 @@ const BANNER = `/* const dirname = __dirname async function main() { - let schemasPath = path.join(dirname, "../../", "crates/theme/schemas") - let schemaFiles = (await fs.readdir(schemasPath)).filter((x) => + const schemasPath = path.join(dirname, "../../", "crates/theme/schemas") + const schemaFiles = (await fs.readdir(schemasPath)).filter((x) => x.endsWith(".json") ) - let compiledTypes = new Set() + const compiledTypes = new Set() - for (let filename of schemaFiles) { - let filePath = path.join(schemasPath, filename) + for (const filename of schemaFiles) { + const filePath = path.join(schemasPath, filename) const fileContents = await fs.readFile(filePath) - let schema = JSON.parse(fileContents.toString()) - let compiled = await compile(schema, schema.title, { + const schema = JSON.parse(fileContents.toString()) + const compiled = await compile(schema, schema.title, { bannerComment: "", }) - let eachType = compiled.split("export") - for (let type of eachType) { + const eachType = compiled.split("export") + for (const type of eachType) { if (!type) { continue } @@ -32,19 +32,18 @@ async function main() { } } - let output = BANNER + Array.from(compiledTypes).join("\n\n") - let outputPath = path.join(dirname, "../../styles/src/types/zed.ts") + const output = BANNER + Array.from(compiledTypes).join("\n\n") + const outputPath = path.join(dirname, "../../styles/src/types/zed.ts") try { - let existing = await fs.readFile(outputPath) + const existing = await fs.readFile(outputPath) if (existing.toString() == output) { // Skip writing if it hasn't changed console.log("Schemas are up to date") return } } catch (e) { - // It's fine if there's no output from a previous run. - // @ts-ignore + // @ts-expect-error - It's fine if there's no output from a previous run. if (e.code !== "ENOENT") { throw e } diff --git a/styles/src/element/interactive.ts b/styles/src/element/interactive.ts index 03a1f7f5ce0e9bf914b6e8671fd14a83278595dc..99b8996aefd7521398880bc7ebeb375a3cb123e3 100644 --- a/styles/src/element/interactive.ts +++ b/styles/src/element/interactive.ts @@ -37,7 +37,7 @@ interface InteractiveProps { * @param state Object containing optional modified fields to be included in the resulting object for each state. * @returns Interactive object with fields from `base` and `state`. */ -export function interactive({ +export function interactive({ base, state, }: InteractiveProps): Interactive { @@ -51,7 +51,7 @@ export function interactive({ defaultState = base ? base : (state.default as T) } - let interactiveObj: Interactive = { + const interactiveObj: Interactive = { default: defaultState, } diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index d98e00383fe019c3fbf5f16168df519140b1c964..a84300283189468dce3de49b430bbf5e848c872c 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -25,7 +25,7 @@ import copilot from "./copilot" import assistant from "./assistant" import { titlebar } from "./titlebar" -export default function app(colorScheme: ColorScheme): Object { +export default function app(colorScheme: ColorScheme): any { return { meta: { name: colorScheme.name, diff --git a/styles/src/styleTree/assistant.ts b/styles/src/styleTree/assistant.ts index 30e109df1a8e092cc71d1af2e8b762ab68dc8e43..f233f0278647b3e143c973f18e1d69eba31e6c0e 100644 --- a/styles/src/styleTree/assistant.ts +++ b/styles/src/styleTree/assistant.ts @@ -3,7 +3,7 @@ import { text, border, background, foreground } from "./components" import editor from "./editor" import { interactive } from "../element" -export default function assistant(colorScheme: ColorScheme) { +export default function assistant(colorScheme: ColorScheme): any { const layer = colorScheme.highest return { container: { diff --git a/styles/src/styleTree/commandPalette.ts b/styles/src/styleTree/commandPalette.ts index 5d4b7373c36169de47219f357a27046acf59d8b1..101f4d437e9a9c478ffbf71e36dea40d5f85c527 100644 --- a/styles/src/styleTree/commandPalette.ts +++ b/styles/src/styleTree/commandPalette.ts @@ -3,8 +3,8 @@ import { withOpacity } from "../theme/color" import { text, background } from "./components" import { toggleable } from "../element" -export default function commandPalette(colorScheme: ColorScheme) { - let layer = colorScheme.highest +export default function commandPalette(colorScheme: ColorScheme): any { + const layer = colorScheme.highest const key = toggleable({ base: { diff --git a/styles/src/styleTree/components.ts b/styles/src/styleTree/components.ts index 3b6f26066bffc26d2d4451adaecd985ef41560ff..8db1fa4b2ef5916f6b871bb1b3f1184b15bc7ee5 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/styleTree/components.ts @@ -211,7 +211,7 @@ export function text( styleOrProperties?: Styles | TextProperties, properties?: TextProperties ) { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) if (typeof styleSetStyleOrProperties === "object") { properties = styleSetStyleOrProperties @@ -220,8 +220,8 @@ export function text( properties = styleOrProperties } - let size = fontSizes[properties?.size || "sm"] - let color = properties?.color || style.foreground + const size = fontSizes[properties?.size || "sm"] + const color = properties?.color || style.foreground return { family: fontFamilies[fontFamily], @@ -273,7 +273,7 @@ export function border( styleOrProperties?: Styles | BorderProperties, properties?: BorderProperties ): Border { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) if (typeof styleSetStyleOrProperties === "object") { properties = styleSetStyleOrProperties @@ -291,9 +291,9 @@ export function border( export function svg( color: string, - asset: String, - width: Number, - height: Number + asset: string, + width: number, + height: number ) { return { color, diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/styleTree/contactFinder.ts index e45647c3d62576f9a2db9644bf635f0562b74cef..f0720d05888127c203b800b72199dbcb5829e718 100644 --- a/styles/src/styleTree/contactFinder.ts +++ b/styles/src/styleTree/contactFinder.ts @@ -3,7 +3,7 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, foreground, text } from "./components" export default function contactFinder(colorScheme: ColorScheme): any { - let layer = colorScheme.middle + const layer = colorScheme.middle const sideMargin = 6 const contactButton = { diff --git a/styles/src/styleTree/contactList.ts b/styles/src/styleTree/contactList.ts index 88ae04277eea5e0b8417da48f38495d513a8a03e..9194e809ee3c24148ed3113a6f5b16fe842019c2 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/styleTree/contactList.ts @@ -1,11 +1,11 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, borderColor, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function contactsPanel(colorScheme: ColorScheme) { +export default function contactsPanel(colorScheme: ColorScheme): any { const nameMargin = 8 const sidePadding = 12 - let layer = colorScheme.middle + const layer = colorScheme.middle const contactButton = { background: background(layer, "on"), diff --git a/styles/src/styleTree/contactNotification.ts b/styles/src/styleTree/contactNotification.ts index 825c5a389acf7bfd522e45557d4b775a3116ba60..450f927535ecf7a8ee596e1c32e03acf4d2d9bf4 100644 --- a/styles/src/styleTree/contactNotification.ts +++ b/styles/src/styleTree/contactNotification.ts @@ -4,8 +4,8 @@ import { interactive } from "../element" const avatarSize = 12 const headerPadding = 8 -export default function contactNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.lowest +export default function contactNotification(colorScheme: ColorScheme): any { + const layer = colorScheme.lowest return { headerAvatar: { height: avatarSize, diff --git a/styles/src/styleTree/contactsPopover.ts b/styles/src/styleTree/contactsPopover.ts index 5946bfb82cf01712d19453a8b3f57018f8eac2b8..6d9f84322d6fe75982d7970e86035f81e2d5c386 100644 --- a/styles/src/styleTree/contactsPopover.ts +++ b/styles/src/styleTree/contactsPopover.ts @@ -1,9 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" +import { background, border } from "./components" -export default function contactsPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - const sidePadding = 12 +export default function contactsPopover(colorScheme: ColorScheme): any { + const layer = colorScheme.middle return { background: background(layer), cornerRadius: 6, diff --git a/styles/src/styleTree/contextMenu.ts b/styles/src/styleTree/contextMenu.ts index a59284c43a4a096e0cd7a739dc5b76ab0158ded5..1064eedd0dc79615aede6fa3faed1387ab04e140 100644 --- a/styles/src/styleTree/contextMenu.ts +++ b/styles/src/styleTree/contextMenu.ts @@ -2,8 +2,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, borderColor, text } from "./components" import { interactive, toggleable } from "../element" -export default function contextMenu(colorScheme: ColorScheme) { - let layer = colorScheme.middle +export default function contextMenu(colorScheme: ColorScheme): any { + const layer = colorScheme.middle return { background: background(layer), cornerRadius: 10, diff --git a/styles/src/styleTree/copilot.ts b/styles/src/styleTree/copilot.ts index 1e09f4ff6b5f9c4c6a8ab9818cd16ede7d58a5e1..2f82e94c43923740b66e5512022d00d1a2f9d585 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/styleTree/copilot.ts @@ -1,12 +1,12 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, foreground, svg, text } from "./components" import { interactive } from "../element" -export default function copilot(colorScheme: ColorScheme) { - let layer = colorScheme.middle +export default function copilot(colorScheme: ColorScheme): any { + const layer = colorScheme.middle - let content_width = 264 + const content_width = 264 - let ctaButton = + const ctaButton = // Copied from welcome screen. FIXME: Move this into a ZDS component interactive({ base: { diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index c53f3ba2ff54d7ec2c48547f4bd77236cad85b77..71c34207eb91705f5a6d48b67549c8987a6d154d 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -6,10 +6,10 @@ import hoverPopover from "./hoverPopover" import { buildSyntax } from "../theme/syntax" import { interactive, toggleable } from "../element" -export default function editor(colorScheme: ColorScheme) { +export default function editor(colorScheme: ColorScheme): any { const { isLight } = colorScheme - let layer = colorScheme.highest + const layer = colorScheme.highest const autocompleteItem = { cornerRadius: 6, diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts index c98cbe768faa2495e5fde6099b685ce4103d0ecb..9b66015dc64ee1aa8c42dd005479643100b62ad4 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/styleTree/feedback.ts @@ -2,8 +2,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, text } from "./components" import { interactive } from "../element" -export default function feedback(colorScheme: ColorScheme) { - let layer = colorScheme.highest +export default function feedback(colorScheme: ColorScheme): any { + const layer = colorScheme.highest return { submit_button: interactive({ diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts index f8988f1f3a42a2791ad70f2128f4454283dc744c..9e2f8d0a785f1d27744a4e97254e3246aad4a9c6 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/styleTree/hoverPopover.ts @@ -1,9 +1,9 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, foreground, text } from "./components" -export default function HoverPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - let baseContainer = { +export default function HoverPopover(colorScheme: ColorScheme): any { + const layer = colorScheme.middle + const baseContainer = { background: background(layer), cornerRadius: 8, padding: { diff --git a/styles/src/styleTree/incomingCallNotification.ts b/styles/src/styleTree/incomingCallNotification.ts index c42558059c7b74be4e384c169049cf0c0155a956..6249bfb6934ae1c074743b61bd95f3fdc0d99f0f 100644 --- a/styles/src/styleTree/incomingCallNotification.ts +++ b/styles/src/styleTree/incomingCallNotification.ts @@ -3,8 +3,8 @@ import { background, border, text } from "./components" export default function incomingCallNotification( colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle +): unknown { + const layer = colorScheme.middle const avatarSize = 48 return { windowHeight: 74, diff --git a/styles/src/styleTree/picker.ts b/styles/src/styleTree/picker.ts index 5501bd4df2f5cd0cd21bcf53f5aa5d307e6ed0e6..aaf2740a6e0419aa51b4c61d51bd034edbff210e 100644 --- a/styles/src/styleTree/picker.ts +++ b/styles/src/styleTree/picker.ts @@ -4,7 +4,7 @@ import { background, border, text } from "./components" import { interactive, toggleable } from "../element" export default function picker(colorScheme: ColorScheme): any { - let layer = colorScheme.lowest + const layer = colorScheme.lowest const container = { background: background(layer), border: border(layer), diff --git a/styles/src/styleTree/projectDiagnostics.ts b/styles/src/styleTree/projectDiagnostics.ts index cf0f07dd8c519d6c8476dabc71fb57bc1e21971f..d2c2152ab416a976ce7ca8098c04b1cf8600af8b 100644 --- a/styles/src/styleTree/projectDiagnostics.ts +++ b/styles/src/styleTree/projectDiagnostics.ts @@ -1,8 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, text } from "./components" -export default function projectDiagnostics(colorScheme: ColorScheme) { - let layer = colorScheme.highest +export default function projectDiagnostics(colorScheme: ColorScheme): any { + const layer = colorScheme.highest return { background: background(layer), tabIconSpacing: 4, diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 3727c1916cb9cd1929de316a6c81f000468b56c5..de4a98df53fd1b038e370365d392b8432d72329b 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -10,10 +10,10 @@ import { } from "./components" import { interactive, toggleable } from "../element" import merge from "ts-deepmerge" -export default function projectPanel(colorScheme: ColorScheme) { +export default function projectPanel(colorScheme: ColorScheme): any { const { isLight } = colorScheme - let layer = colorScheme.middle + const layer = colorScheme.middle type EntryStateProps = { background?: string diff --git a/styles/src/styleTree/projectSharedNotification.ts b/styles/src/styleTree/projectSharedNotification.ts index d05eb1b0c51953b34ac7b08727c81447a66bc047..c114e17176a20cd524552e155df923e2e6c37cc6 100644 --- a/styles/src/styleTree/projectSharedNotification.ts +++ b/styles/src/styleTree/projectSharedNotification.ts @@ -3,8 +3,8 @@ import { background, border, text } from "./components" export default function projectSharedNotification( colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle +): unknown { + const layer = colorScheme.middle const avatarSize = 48 return { diff --git a/styles/src/styleTree/search.ts b/styles/src/styleTree/search.ts index 9a86d1d558db1c6a7d54e86e693c4bac8f076aea..9ecad3ab1973f61c11439f9c50c1f4093c022fe4 100644 --- a/styles/src/styleTree/search.ts +++ b/styles/src/styleTree/search.ts @@ -3,7 +3,7 @@ import { withOpacity } from "../theme/color" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function search(colorScheme: ColorScheme): unknown { +export default function search(colorScheme: ColorScheme): any { const layer = colorScheme.highest // Search input diff --git a/styles/src/styleTree/sharedScreen.ts b/styles/src/styleTree/sharedScreen.ts index a58e7e022290ec9bc0f3b79aea1d17f50fd9fd62..59968d59499c10cf03904bebb7270e47036dab11 100644 --- a/styles/src/styleTree/sharedScreen.ts +++ b/styles/src/styleTree/sharedScreen.ts @@ -2,8 +2,10 @@ import { ColorScheme } from "../theme/colorScheme" import { StyleTree } from "../types" import { background } from "./components" -export default function sharedScreen(colorScheme: ColorScheme): StyleTree.SharedScreen { - let layer = colorScheme.highest +export default function sharedScreen( + colorScheme: ColorScheme +): StyleTree.SharedScreen { + const layer = colorScheme.highest return { background: background(layer), } diff --git a/styles/src/styleTree/simpleMessageNotification.ts b/styles/src/styleTree/simpleMessageNotification.ts index e894db3514ad19f70f24a0458c303b397f426f8a..99dc878a79a4b44c54c5e9a90a3647c4a922523c 100644 --- a/styles/src/styleTree/simpleMessageNotification.ts +++ b/styles/src/styleTree/simpleMessageNotification.ts @@ -6,8 +6,8 @@ const headerPadding = 8 export default function simpleMessageNotification( colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle +): unknown { + const layer = colorScheme.middle return { message: { ...text(layer, "sans", { size: "xs" }), diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/styleTree/statusBar.ts index 339e2e40cf25535ac5e682b059bbe8e504e61bbf..6ca53afb187b866e4a3a5bfa561db992845538c0 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/styleTree/statusBar.ts @@ -1,8 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function statusBar(colorScheme: ColorScheme) { - let layer = colorScheme.lowest +export default function statusBar(colorScheme: ColorScheme): any { + const layer = colorScheme.lowest const statusContainer = { cornerRadius: 6, diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index af35a8fef4c379471f172950f96520626045a14c..ae9512d8ce57a50482892c22d860bcb7a5db7084 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -3,11 +3,11 @@ import { withOpacity } from "../theme/color" import { text, border, background, foreground } from "./components" import { interactive, toggleable } from "../element" -export default function tabBar(colorScheme: ColorScheme) { +export default function tabBar(colorScheme: ColorScheme): any { const height = 32 - let activeLayer = colorScheme.highest - let layer = colorScheme.middle + const activeLayer = colorScheme.highest + const layer = colorScheme.middle const tab = { height, diff --git a/styles/src/styleTree/titlebar.ts b/styles/src/styleTree/titlebar.ts index 3c7318a56e5015ce4f4a9e5b3fb00cd94e2b4ca5..a5bcdc9492d38a20f0e2863884f0ac9b2c447f44 100644 --- a/styles/src/styleTree/titlebar.ts +++ b/styles/src/styleTree/titlebar.ts @@ -155,7 +155,7 @@ function user_menu(theme: ColorScheme) { } } -export function titlebar(theme: ColorScheme) { +export function titlebar(theme: ColorScheme): any { const avatarWidth = 15 const avatarOuterWidth = avatarWidth + 4 const followerAvatarWidth = 14 diff --git a/styles/src/styleTree/toggle.ts b/styles/src/styleTree/toggle.ts deleted file mode 100644 index 2b6858e15be6c3db85232371ba38e0bc9127ea33..0000000000000000000000000000000000000000 --- a/styles/src/styleTree/toggle.ts +++ /dev/null @@ -1,47 +0,0 @@ -import merge from "ts-deepmerge" - -type ToggleState = "inactive" | "active" - -type Toggleable = Record - -const NO_INACTIVE_OR_BASE_ERROR = - "A toggleable object must have an inactive state, or a base property." -const NO_ACTIVE_ERROR = "A toggleable object must have an active state." - -interface ToggleableProps { - base?: T - state: Partial> -} - -/** - * Helper function for creating Toggleable objects. - * @template T The type of the object being toggled. - * @param props Object containing the base (inactive) state and state modifications to create the active state. - * @returns A Toggleable object containing both the inactive and active states. - * @example - * ``` - * toggleable({ - * base: { background: "#000000", text: "#CCCCCC" }, - * state: { active: { text: "#CCCCCC" } }, - * }) - * ``` - */ -export function toggleable( - props: ToggleableProps -): Toggleable { - const { base, state } = props - - if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR) - if (!state.active) throw new Error(NO_ACTIVE_ERROR) - - const inactiveState = base - ? ((state.inactive ? merge(base, state.inactive) : base) as T) - : (state.inactive as T) - - const toggleObj: Toggleable = { - inactive: inactiveState, - active: merge(base ?? {}, state.active) as T, - } - - return toggleObj -} diff --git a/styles/src/styleTree/toolbarDropdownMenu.ts b/styles/src/styleTree/toolbarDropdownMenu.ts index d82e5f1cde6ed4543dc3bbf4b9f3d224952da33e..4eff06026b0551e3671578ca644481eaf2135f46 100644 --- a/styles/src/styleTree/toolbarDropdownMenu.ts +++ b/styles/src/styleTree/toolbarDropdownMenu.ts @@ -1,8 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" -export default function dropdownMenu(colorScheme: ColorScheme) { - let layer = colorScheme.middle +export default function dropdownMenu(colorScheme: ColorScheme): any { + const layer = colorScheme.middle return { rowHeight: 30, diff --git a/styles/src/styleTree/tooltip.ts b/styles/src/styleTree/tooltip.ts index 1666ce5658776bd8c3072ee98a3233bcb8cf5a68..fb896112a9990059c90e2785ee475999f478b11a 100644 --- a/styles/src/styleTree/tooltip.ts +++ b/styles/src/styleTree/tooltip.ts @@ -1,8 +1,8 @@ import { ColorScheme } from "../theme/colorScheme" import { background, border, text } from "./components" -export default function tooltip(colorScheme: ColorScheme) { - let layer = colorScheme.middle +export default function tooltip(colorScheme: ColorScheme): any { + const layer = colorScheme.middle return { background: background(layer), border: border(layer), diff --git a/styles/src/styleTree/updateNotification.ts b/styles/src/styleTree/updateNotification.ts index c6ef81c667c77a3235aece08e1f8f7256b012e6c..bf792ffc7b5bfa04b948b8fa4d66264d33b53b51 100644 --- a/styles/src/styleTree/updateNotification.ts +++ b/styles/src/styleTree/updateNotification.ts @@ -4,8 +4,8 @@ import { interactive } from "../element" const headerPadding = 8 -export default function updateNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.middle +export default function updateNotification(colorScheme: ColorScheme): any { + const layer = colorScheme.middle return { message: { ...text(layer, "sans", { size: "xs" }), diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts index 3b3eeba53ad273862d09427b88ac999ee994f8c1..c64a17b5f60ec194ac167710cd845aafb8da2296 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/styleTree/welcome.ts @@ -10,7 +10,7 @@ import { } from "./components" import { interactive } from "../element" -export default function welcome(colorScheme: ColorScheme) { +export default function welcome(colorScheme: ColorScheme): any { const layer = colorScheme.highest const checkboxBase = { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index afc2ea4d9869f0ade31bdac1840f80ba131215d6..5f5da7e47ea5ccadfa41189f155813a83b9adc3f 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -13,7 +13,7 @@ import tabBar from "./tabBar" import { interactive } from "../element" import { titlebar } from "./titlebar" -export default function workspace(colorScheme: ColorScheme) { +export default function workspace(colorScheme: ColorScheme): any { const layer = colorScheme.lowest const isLight = colorScheme.isLight diff --git a/styles/src/system/lib/convert.ts b/styles/src/system/lib/convert.ts deleted file mode 100644 index 998f95a636383d9e2c622133f2e2c09a17336672..0000000000000000000000000000000000000000 --- a/styles/src/system/lib/convert.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** Converts a percentage scale value (0-100) to normalized scale (0-1) value. */ -export function percentageToNormalized(value: number) { - const normalized = value / 100 - return normalized -} - -/** Converts a normalized scale (0-1) value to a percentage scale (0-100) value. */ -export function normalizedToPercetage(value: number) { - const percentage = value * 100 - return percentage -} diff --git a/styles/src/system/lib/curve.ts b/styles/src/system/lib/curve.ts deleted file mode 100644 index b24f2948cf3f2b6e4c47ee36d4dd5ed9934fa28b..0000000000000000000000000000000000000000 --- a/styles/src/system/lib/curve.ts +++ /dev/null @@ -1,26 +0,0 @@ -import bezier from "bezier-easing" -import { Curve } from "../ref/curves" - -/** - * Formats our Curve data structure into a bezier easing function. - * @param {Curve} curve - The curve to format. - * @param {Boolean} inverted - Whether or not to invert the curve. - * @returns {EasingFunction} The formatted easing function. - */ -export function curve(curve: Curve, inverted?: Boolean) { - if (inverted) { - return bezier( - curve.value[3], - curve.value[2], - curve.value[1], - curve.value[0] - ) - } - - return bezier( - curve.value[0], - curve.value[1], - curve.value[2], - curve.value[3] - ) -} diff --git a/styles/src/system/lib/generate.ts b/styles/src/system/lib/generate.ts deleted file mode 100644 index 40f7a9154c18d7b7a6cabba474eb2befb56af492..0000000000000000000000000000000000000000 --- a/styles/src/system/lib/generate.ts +++ /dev/null @@ -1,159 +0,0 @@ -import bezier from "bezier-easing" -import chroma from "chroma-js" -import { Color, ColorFamily, ColorFamilyConfig, ColorScale } from "../types" -import { percentageToNormalized } from "./convert" -import { curve } from "./curve" - -// Re-export interface in a more standard format -export type EasingFunction = bezier.EasingFunction - -/** - * Generates a color, outputs it in multiple formats, and returns a variety of useful metadata. - * - * @param {EasingFunction} hueEasing - An easing function for the hue component of the color. - * @param {EasingFunction} saturationEasing - An easing function for the saturation component of the color. - * @param {EasingFunction} lightnessEasing - An easing function for the lightness component of the color. - * @param {ColorFamilyConfig} family - Configuration for the color family. - * @param {number} step - The current step. - * @param {number} steps - The total number of steps in the color scale. - * - * @returns {Color} The generated color, with its calculated contrast against black and white, as well as its LCH values, RGBA array, hexadecimal representation, and a flag indicating if it is light or dark. - */ -function generateColor( - hueEasing: EasingFunction, - saturationEasing: EasingFunction, - lightnessEasing: EasingFunction, - family: ColorFamilyConfig, - step: number, - steps: number -) { - const { hue, saturation, lightness } = family.color - - const stepHue = hueEasing(step / steps) * (hue.end - hue.start) + hue.start - const stepSaturation = - saturationEasing(step / steps) * (saturation.end - saturation.start) + - saturation.start - const stepLightness = - lightnessEasing(step / steps) * (lightness.end - lightness.start) + - lightness.start - - const color = chroma.hsl( - stepHue, - percentageToNormalized(stepSaturation), - percentageToNormalized(stepLightness) - ) - - const contrast = { - black: { - value: chroma.contrast(color, "black"), - aaPass: chroma.contrast(color, "black") >= 4.5, - aaaPass: chroma.contrast(color, "black") >= 7, - }, - white: { - value: chroma.contrast(color, "white"), - aaPass: chroma.contrast(color, "white") >= 4.5, - aaaPass: chroma.contrast(color, "white") >= 7, - }, - } - - const lch = color.lch() - const rgba = color.rgba() - const hex = color.hex() - - // 55 is a magic number. It's the lightness value at which we consider a color to be "light". - // It was picked by eye with some testing. We might want to use a more scientific approach in the future. - const isLight = lch[0] > 55 - - const result: Color = { - step, - lch, - hex, - rgba, - contrast, - isLight, - } - - return result -} - -/** - * Generates a color scale based on a color family configuration. - * - * @param {ColorFamilyConfig} config - The configuration for the color family. - * @param {Boolean} inverted - Specifies whether the color scale should be inverted or not. - * - * @returns {ColorScale} The generated color scale. - * - * @example - * ```ts - * const colorScale = generateColorScale({ - * name: "blue", - * color: { - * hue: { - * start: 210, - * end: 240, - * curve: "easeInOut" - * }, - * saturation: { - * start: 100, - * end: 100, - * curve: "easeInOut" - * }, - * lightness: { - * start: 50, - * end: 50, - * curve: "easeInOut" - * } - * } - * }); - * ``` - */ - -export function generateColorScale( - config: ColorFamilyConfig, - inverted: Boolean = false -) { - const { hue, saturation, lightness } = config.color - - // 101 steps means we get values from 0-100 - const NUM_STEPS = 101 - - const hueEasing = curve(hue.curve, inverted) - const saturationEasing = curve(saturation.curve, inverted) - const lightnessEasing = curve(lightness.curve, inverted) - - let scale: ColorScale = { - colors: [], - values: [], - } - - for (let i = 0; i < NUM_STEPS; i++) { - const color = generateColor( - hueEasing, - saturationEasing, - lightnessEasing, - config, - i, - NUM_STEPS - ) - - scale.colors.push(color) - scale.values.push(color.hex) - } - - return scale -} - -/** Generates a color family with a scale and an inverted scale. */ -export function generateColorFamily(config: ColorFamilyConfig) { - const scale = generateColorScale(config, false) - const invertedScale = generateColorScale(config, true) - - const family: ColorFamily = { - name: config.name, - scale, - invertedScale, - } - - return family -} diff --git a/styles/src/system/ref/color.ts b/styles/src/system/ref/color.ts deleted file mode 100644 index 6c0b53c35b08284643d316bbb0f49161c8ab25f8..0000000000000000000000000000000000000000 --- a/styles/src/system/ref/color.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { generateColorFamily } from "../lib/generate" -import { curve } from "./curves" - -// These are the source colors for the color scales in the system. -// These should never directly be used directly in components or themes as they generate thousands of lines of code. -// Instead, use the outputs from the reference palette which exports a smaller subset of colors. - -// Token or user-facing colors should use short, clear names and a 100-900 scale to match the font weight scale. - -// Light Gray ======================================== // - -export const lightgray = generateColorFamily({ - name: "lightgray", - color: { - hue: { - start: 210, - end: 210, - curve: curve.linear, - }, - saturation: { - start: 10, - end: 15, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 50, - curve: curve.linear, - }, - }, -}) - -// Light Dark ======================================== // - -export const darkgray = generateColorFamily({ - name: "darkgray", - color: { - hue: { - start: 210, - end: 210, - curve: curve.linear, - }, - saturation: { - start: 15, - end: 20, - curve: curve.saturation, - }, - lightness: { - start: 55, - end: 8, - curve: curve.linear, - }, - }, -}) - -// Red ======================================== // - -export const red = generateColorFamily({ - name: "red", - color: { - hue: { - start: 0, - end: 0, - curve: curve.linear, - }, - saturation: { - start: 95, - end: 75, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 25, - curve: curve.lightness, - }, - }, -}) - -// Sunset ======================================== // - -export const sunset = generateColorFamily({ - name: "sunset", - color: { - hue: { - start: 15, - end: 15, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 25, - curve: curve.lightness, - }, - }, -}) - -// Orange ======================================== // - -export const orange = generateColorFamily({ - name: "orange", - color: { - hue: { - start: 25, - end: 25, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 95, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Amber ======================================== // - -export const amber = generateColorFamily({ - name: "amber", - color: { - hue: { - start: 38, - end: 38, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 100, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Yellow ======================================== // - -export const yellow = generateColorFamily({ - name: "yellow", - color: { - hue: { - start: 48, - end: 48, - curve: curve.linear, - }, - saturation: { - start: 90, - end: 100, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Lemon ======================================== // - -export const lemon = generateColorFamily({ - name: "lemon", - color: { - hue: { - start: 55, - end: 55, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 95, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Citron ======================================== // - -export const citron = generateColorFamily({ - name: "citron", - color: { - hue: { - start: 70, - end: 70, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Lime ======================================== // - -export const lime = generateColorFamily({ - name: "lime", - color: { - hue: { - start: 85, - end: 85, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 80, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Green ======================================== // - -export const green = generateColorFamily({ - name: "green", - color: { - hue: { - start: 108, - end: 108, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Mint ======================================== // - -export const mint = generateColorFamily({ - name: "mint", - color: { - hue: { - start: 142, - end: 142, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 75, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Cyan ======================================== // - -export const cyan = generateColorFamily({ - name: "cyan", - color: { - hue: { - start: 179, - end: 179, - curve: curve.linear, - }, - saturation: { - start: 70, - end: 80, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Sky ======================================== // - -export const sky = generateColorFamily({ - name: "sky", - color: { - hue: { - start: 195, - end: 205, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Blue ======================================== // - -export const blue = generateColorFamily({ - name: "blue", - color: { - hue: { - start: 218, - end: 218, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Indigo ======================================== // - -export const indigo = generateColorFamily({ - name: "indigo", - color: { - hue: { - start: 245, - end: 245, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 50, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 22, - curve: curve.lightness, - }, - }, -}) - -// Purple ======================================== // - -export const purple = generateColorFamily({ - name: "purple", - color: { - hue: { - start: 260, - end: 270, - curve: curve.linear, - }, - saturation: { - start: 65, - end: 55, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Pink ======================================== // - -export const pink = generateColorFamily({ - name: "pink", - color: { - hue: { - start: 320, - end: 330, - curve: curve.linear, - }, - saturation: { - start: 70, - end: 65, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 32, - curve: curve.lightness, - }, - }, -}) - -// Rose ======================================== // - -export const rose = generateColorFamily({ - name: "rose", - color: { - hue: { - start: 345, - end: 345, - curve: curve.linear, - }, - saturation: { - start: 90, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 32, - curve: curve.lightness, - }, - }, -}) diff --git a/styles/src/system/ref/curves.ts b/styles/src/system/ref/curves.ts deleted file mode 100644 index 02002dbe9befd605b3a57400684b2f37bf7b642b..0000000000000000000000000000000000000000 --- a/styles/src/system/ref/curves.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface Curve { - name: string - value: number[] -} - -export interface Curves { - lightness: Curve - saturation: Curve - linear: Curve -} - -export const curve: Curves = { - lightness: { - name: "lightnessCurve", - value: [0.2, 0, 0.75, 1.0], - }, - saturation: { - name: "saturationCurve", - value: [0.67, 0.6, 0.55, 1.0], - }, - linear: { - name: "linear", - value: [0.5, 0.5, 0.5, 0.5], - }, -} diff --git a/styles/src/system/system.ts b/styles/src/system/system.ts deleted file mode 100644 index 619b0795c89770920b886c20a508da23d9b6eed9..0000000000000000000000000000000000000000 --- a/styles/src/system/system.ts +++ /dev/null @@ -1,32 +0,0 @@ -import chroma from "chroma-js" -import * as colorFamily from "./ref/color" - -const color = { - lightgray: chroma - .scale(colorFamily.lightgray.scale.values) - .mode("lch") - .colors(9), - darkgray: chroma - .scale(colorFamily.darkgray.scale.values) - .mode("lch") - .colors(9), - red: chroma.scale(colorFamily.red.scale.values).mode("lch").colors(9), - sunset: chroma.scale(colorFamily.sunset.scale.values).mode("lch").colors(9), - orange: chroma.scale(colorFamily.orange.scale.values).mode("lch").colors(9), - amber: chroma.scale(colorFamily.amber.scale.values).mode("lch").colors(9), - yellow: chroma.scale(colorFamily.yellow.scale.values).mode("lch").colors(9), - lemon: chroma.scale(colorFamily.lemon.scale.values).mode("lch").colors(9), - citron: chroma.scale(colorFamily.citron.scale.values).mode("lch").colors(9), - lime: chroma.scale(colorFamily.lime.scale.values).mode("lch").colors(9), - green: chroma.scale(colorFamily.green.scale.values).mode("lch").colors(9), - mint: chroma.scale(colorFamily.mint.scale.values).mode("lch").colors(9), - cyan: chroma.scale(colorFamily.cyan.scale.values).mode("lch").colors(9), - sky: chroma.scale(colorFamily.sky.scale.values).mode("lch").colors(9), - blue: chroma.scale(colorFamily.blue.scale.values).mode("lch").colors(9), - indigo: chroma.scale(colorFamily.indigo.scale.values).mode("lch").colors(9), - purple: chroma.scale(colorFamily.purple.scale.values).mode("lch").colors(9), - pink: chroma.scale(colorFamily.pink.scale.values).mode("lch").colors(9), - rose: chroma.scale(colorFamily.rose.scale.values).mode("lch").colors(9), -} - -export { color } diff --git a/styles/src/system/types.ts b/styles/src/system/types.ts deleted file mode 100644 index 8de65a37eb131ba412fe269529106d58070669ee..0000000000000000000000000000000000000000 --- a/styles/src/system/types.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Curve } from "./ref/curves" - -export interface ColorAccessibilityValue { - value: number - aaPass: boolean - aaaPass: boolean -} - -/** - * Calculates the color contrast between a specified color and its corresponding background and foreground colors. - * - * @note This implementation is currently basic – Currently we only calculate contrasts against black and white, in the future will allow for dynamic color contrast calculation based on the colors present in a given palette. - * @note The goal is to align with WCAG3 accessibility standards as they become stabilized. See the [WCAG 3 Introduction](https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/) for more information. - */ -export interface ColorAccessibility { - black: ColorAccessibilityValue - white: ColorAccessibilityValue -} - -export type Color = { - step: number - contrast: ColorAccessibility - hex: string - lch: number[] - rgba: number[] - isLight: boolean -} - -export interface ColorScale { - colors: Color[] - // An array of hex values for each color in the scale - values: string[] -} - -export type ColorFamily = { - name: string - scale: ColorScale - invertedScale: ColorScale -} - -export interface ColorFamilyHue { - start: number - end: number - curve: Curve -} - -export interface ColorFamilySaturation { - start: number - end: number - curve: Curve -} - -export interface ColorFamilyLightness { - start: number - end: number - curve: Curve -} - -export interface ColorFamilyConfig { - name: string - color: { - hue: ColorFamilyHue - saturation: ColorFamilySaturation - lightness: ColorFamilyLightness - } -} diff --git a/styles/src/theme/colorScheme.ts b/styles/src/theme/colorScheme.ts index 9a810730867ad82fa57265a8232ab397a51d84f4..ea3b1b9b2980be2651258f4f064658b28f13efca 100644 --- a/styles/src/theme/colorScheme.ts +++ b/styles/src/theme/colorScheme.ts @@ -218,9 +218,9 @@ function buildStyleSet( ramp: Scale, backgroundBase: number, foregroundBase: number, - step: number = 0.08 + step = 0.08 ): StyleSet { - let styleDefinitions = buildStyleDefinition( + const styleDefinitions = buildStyleDefinition( backgroundBase, foregroundBase, step @@ -255,7 +255,7 @@ function buildStyleSet( function buildStyleDefinition( bgBase: number, fgBase: number, - step: number = 0.08 + step = 0.08 ) { return { background: { diff --git a/styles/src/theme/ramps.ts b/styles/src/theme/ramps.ts index f8c44ba3f97e5c24814d5df68ce58a8a97aafb72..de1f8ee0d4ca6bb33853ef65fa67eb16f715a9ce 100644 --- a/styles/src/theme/ramps.ts +++ b/styles/src/theme/ramps.ts @@ -6,8 +6,8 @@ import { } from "./themeConfig" export function colorRamp(color: Color): Scale { - let endColor = color.desaturate(1).brighten(5) - let startColor = color.desaturate(1).darken(4) + const endColor = color.desaturate(1).brighten(5) + const startColor = color.desaturate(1).darken(4) return chroma.scale([startColor, color, endColor]).mode("lab") } @@ -18,15 +18,15 @@ export function colorRamp(color: Color): Scale { theme so that we don't modify the passed in ramps. This combined with an error in the type definitions for chroma js means we have to cast the colors function to any in order to get the colors back out from the original ramps. - * @param isLight - * @param colorRamps - * @returns + * @param isLight + * @param colorRamps + * @returns */ export function getRamps( isLight: boolean, colorRamps: ThemeConfigInputColors ): RampSet { - const ramps: RampSet = {} as any + const ramps: RampSet = {} as any // eslint-disable-line @typescript-eslint/no-explicit-any const colorsKeys = Object.keys(colorRamps) as ThemeConfigInputColorsKeys[] if (isLight) { diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index 369fceb070ea906178b1dc5b7ee37ff2d737df60..a35013c1e9a941cd07cefa7c8d990213e8df1b8c 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -56,7 +56,7 @@ export interface Syntax { // == Types ====== / // We allow Function here because all JS objects literals have this property - constructor: SyntaxHighlightStyle | Function + constructor: SyntaxHighlightStyle | Function // eslint-disable-line @typescript-eslint/ban-types variant: SyntaxHighlightStyle type: SyntaxHighlightStyle // js: predefined_type diff --git a/styles/src/themes/gruvbox/gruvbox-common.ts b/styles/src/themes/gruvbox/gruvbox-common.ts index 18e8c5b97e5e75f050f83ff9605f3a68ca0bf110..37850fe019c1bc186b21de6a59a2bc930a139eec 100644 --- a/styles/src/themes/gruvbox/gruvbox-common.ts +++ b/styles/src/themes/gruvbox/gruvbox-common.ts @@ -177,30 +177,29 @@ const buildVariant = (variant: Variant): ThemeConfig => { let neutral: string[] = [] switch (variant.name) { - case "Dark Hard": { + case "Dark Hard": neutral = darkHardNeutral break - } - case "Dark": { + + case "Dark": neutral = darkNeutral break - } - case "Dark Soft": { + + case "Dark Soft": neutral = darkSoftNeutral break - } - case "Light Hard": { + + case "Light Hard": neutral = lightHardNeutral break - } - case "Light": { + + case "Light": neutral = lightNeutral break - } - case "Light Soft": { + + case "Light Soft": neutral = lightSoftNeutral break - } } const ramps = { diff --git a/styles/src/types/element.ts b/styles/src/types/element.ts index 6f6bb91e58df016b0a46266ac73ac7b0d84743f5..d6da5f9b714d05db89181d41702c6db1d9273569 100644 --- a/styles/src/types/element.ts +++ b/styles/src/types/element.ts @@ -1,4 +1,4 @@ import { Clean } from "./util" -import * as zed from './zed' +import * as zed from "./zed" export type Text = Clean diff --git a/styles/src/types/index.ts b/styles/src/types/index.ts index e1f55a97e2228acb5e8eaa5e02a32f50beec905c..3a017feb280589b65ea35966bb8bc1368b29c72e 100644 --- a/styles/src/types/index.ts +++ b/styles/src/types/index.ts @@ -1,5 +1,5 @@ -import * as StyleTree from './styleTree' -import * as Property from './property' -import * as Element from './element' +import * as StyleTree from "./styleTree" +import * as Property from "./property" +import * as Element from "./element" export { StyleTree, Property, Element } diff --git a/styles/src/types/property.ts b/styles/src/types/property.ts index 97867c9858774b0672d23e3afca9523f84b691c5..6205b725ef02782478f583c36d700788643488eb 100644 --- a/styles/src/types/property.ts +++ b/styles/src/types/property.ts @@ -1,5 +1,5 @@ -import { Clean } from './util' -import * as zed from './zed' +import { Clean } from "./util" +import * as zed from "./zed" export type Color = zed.Color export type CursorStyle = zed.CursorStyle diff --git a/styles/src/types/styleTree.ts b/styles/src/types/styleTree.ts index c4194ca12d2ff33dec391db443f5bcdc33be577e..08ae6833497367cbb6a90943072cc78f43a37ff5 100644 --- a/styles/src/types/styleTree.ts +++ b/styles/src/types/styleTree.ts @@ -1,29 +1,33 @@ -import { Clean } from './util' -import * as zed from './zed' +import { Clean } from "./util" +import * as zed from "./zed" -export type AssistantStyle = Readonly>; -export type CommandPalette = Readonly>; -export type ContactFinder = Readonly>; -export type ContactList = Readonly>; -export type ContactNotification = Readonly>; -export type ContactsPopover = Readonly>; -export type ContextMenu = Readonly>; -export type Copilot = Readonly>; -export type Editor = Readonly>; -export type FeedbackStyle = Readonly>; -export type IncomingCallNotification = Readonly>; -export type ThemeMeta = Readonly>; -export type Picker = Readonly>; -export type ProjectDiagnostics = Readonly>; -export type ProjectPanel = Readonly>; -export type ProjectSharedNotification = Readonly>; -export type Search = Readonly>; -export type SharedScreen = Readonly>; -export type MessageNotification = Readonly>; -export type TerminalStyle = Readonly>; -export type UserMenu = Readonly>; -export type DropdownMenu = Readonly>; -export type TooltipStyle = Readonly>; -export type UpdateNotification = Readonly>; -export type WelcomeStyle = Readonly>; -export type Workspace = Readonly>; +export type AssistantStyle = Readonly> +export type CommandPalette = Readonly> +export type ContactFinder = Readonly> +export type ContactList = Readonly> +export type ContactNotification = Readonly> +export type ContactsPopover = Readonly> +export type ContextMenu = Readonly> +export type Copilot = Readonly> +export type Editor = Readonly> +export type FeedbackStyle = Readonly> +export type IncomingCallNotification = Readonly< + Clean +> +export type ThemeMeta = Readonly> +export type Picker = Readonly> +export type ProjectDiagnostics = Readonly> +export type ProjectPanel = Readonly> +export type ProjectSharedNotification = Readonly< + Clean +> +export type Search = Readonly> +export type SharedScreen = Readonly> +export type MessageNotification = Readonly> +export type TerminalStyle = Readonly> +export type UserMenu = Readonly> +export type DropdownMenu = Readonly> +export type TooltipStyle = Readonly> +export type UpdateNotification = Readonly> +export type WelcomeStyle = Readonly> +export type Workspace = Readonly> diff --git a/styles/src/types/util.ts b/styles/src/types/util.ts index 851acd4b180217a7b7da5fb1102b528d4440c73d..99a742124a378855ed73125a5d573dee756df455 100644 --- a/styles/src/types/util.ts +++ b/styles/src/types/util.ts @@ -1,15 +1,17 @@ export type Prettify = { - [K in keyof T]: T[K]; -} & unknown; + [K in keyof T]: T[K] +} & unknown /** -* Clean removes the [k: string]: unknown property from an object, -* and Prettifies it, providing better hover information for the type -*/ + * Clean removes the [k: string]: unknown property from an object, + * and Prettifies it, providing better hover information for the type + */ export type Clean = { - [K in keyof T as string extends K ? never : K]: T[K]; + [K in keyof T as string extends K ? never : K]: T[K] } export type DeepClean = { - [K in keyof T as string extends K ? never : K]: T[K] extends object ? DeepClean : T[K]; + [K in keyof T as string extends K ? never : K]: T[K] extends object + ? DeepClean + : T[K] } diff --git a/styles/src/utils/slugify.ts b/styles/src/utils/slugify.ts index b5c3b3c5196f51b29319a826b5e76785455be8b4..04fd4d53bba14be9b7c5e7d2c89231a0066aaf49 100644 --- a/styles/src/utils/slugify.ts +++ b/styles/src/utils/slugify.ts @@ -3,8 +3,8 @@ export function slugify(t: string): string { .toString() .toLowerCase() .replace(/\s+/g, "-") - .replace(/[^\w\-]+/g, "") - .replace(/\-\-+/g, "-") + .replace(/[^\w-]+/g, "") + .replace(/--+/g, "-") .replace(/^-+/, "") .replace(/-+$/, "") } diff --git a/styles/src/utils/snakeCase.ts b/styles/src/utils/snakeCase.ts index 519106470783da85ebee364752a7a4e86eb879e7..38c8a90a9e864177dcea255fa5b87a87da5a9607 100644 --- a/styles/src/utils/snakeCase.ts +++ b/styles/src/utils/snakeCase.ts @@ -5,8 +5,8 @@ import { snakeCase } from "case-anything" // Typescript magic to convert any string from camelCase to snake_case at compile time type SnakeCase = S extends string ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S + ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + : S : S type SnakeCased = { @@ -14,7 +14,7 @@ type SnakeCased = { } export default function snakeCaseTree(object: T): SnakeCased { - const snakeObject: any = {} + const snakeObject: any = {} // eslint-disable-line @typescript-eslint/no-explicit-any for (const key in object) { snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = snakeCaseValue(object[key]) @@ -22,6 +22,7 @@ export default function snakeCaseTree(object: T): SnakeCased { return snakeObject } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function snakeCaseValue(value: any): any { if (typeof value === "object") { if (Array.isArray(value)) { diff --git a/styles/tsconfig.json b/styles/tsconfig.json index 7a8ec69927c92bc0a50981c75434336e393fa6d0..cf68509748e64500cdc75f2e64ebecee06265814 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -21,16 +21,36 @@ "experimentalDecorators": true, "strictPropertyInitialization": false, "skipLibCheck": true, + "useUnknownInCatchVariables": false, "baseUrl": ".", "paths": { - "@/*": ["./*"], - "@element/*": ["./src/element/*"], - "@component/*": ["./src/component/*"], - "@styleTree/*": ["./src/styleTree/*"], - "@theme/*": ["./src/theme/*"], - "@themes/*": ["./src/themes/*"], - "@util/*": ["./src/util/*"] + "@/*": [ + "./*" + ], + "@element/*": [ + "./src/element/*" + ], + "@component/*": [ + "./src/component/*" + ], + "@styleTree/*": [ + "./src/styleTree/*" + ], + "@theme/*": [ + "./src/theme/*" + ], + "@types/*": [ + "./src/util/*" + ], + "@themes/*": [ + "./src/themes/*" + ], + "@util/*": [ + "./src/util/*" + ] } }, - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } From 2e162f8af7e561b504f98b4c47186689044277ff Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 28 Jun 2023 18:20:43 -0400 Subject: [PATCH 027/169] WIP convert to `snake_case` --- styles/mod.py | 42 +++++++++++ styles/package.json | 8 +- .../{buildLicenses.ts => build_licenses.ts} | 0 .../src/{buildThemes.ts => build_themes.ts} | 6 +- .../src/{buildTokens.ts => build_tokens.ts} | 6 +- styles/src/{buildTypes.ts => build_types.ts} | 1 - styles/src/component/icon_button.ts | 10 +-- styles/src/component/text_button.ts | 10 +-- styles/src/styleTree/app.ts | 75 ------------------- styles/src/style_tree/app.ts | 75 +++++++++++++++++++ .../{styleTree => style_tree}/assistant.ts | 2 +- .../command_palette.ts} | 4 +- .../{styleTree => style_tree}/components.ts | 2 +- .../contact_finder.ts} | 4 +- .../contact_list.ts} | 4 +- .../contact_notification.ts} | 4 +- .../contacts_popover.ts} | 4 +- .../context_menu.ts} | 4 +- .../src/{styleTree => style_tree}/copilot.ts | 2 +- .../src/{styleTree => style_tree}/editor.ts | 18 ++--- .../src/{styleTree => style_tree}/feedback.ts | 2 +- .../hover_popover.ts} | 4 +- .../incoming_call_notification.ts} | 4 +- .../src/{styleTree => style_tree}/picker.ts | 2 +- .../project_diagnostics.ts} | 4 +- .../project_panel.ts} | 28 +++---- .../project_shared_notification.ts} | 4 +- .../src/{styleTree => style_tree}/search.ts | 2 +- .../shared_screen.ts} | 2 +- .../simple_message_notification.ts} | 4 +- .../statusBar.ts => style_tree/status_bar.ts} | 4 +- .../tabBar.ts => style_tree/tab_bar.ts} | 4 +- .../src/{styleTree => style_tree}/terminal.ts | 2 +- .../src/{styleTree => style_tree}/titlebar.ts | 0 .../toolbar_dropdown_menu.ts} | 4 +- .../src/{styleTree => style_tree}/tooltip.ts | 2 +- .../update_notification.ts} | 4 +- .../src/{styleTree => style_tree}/welcome.ts | 2 +- .../{styleTree => style_tree}/workspace.ts | 16 ++-- .../theme/{colorScheme.ts => color_scheme.ts} | 6 +- styles/src/theme/index.ts | 4 +- styles/src/theme/ramps.ts | 4 +- styles/src/theme/syntax.ts | 2 +- .../theme/{themeConfig.ts => theme_config.ts} | 0 .../{colorScheme.ts => color_scheme.ts} | 6 +- styles/src/theme/tokens/layer.ts | 2 +- styles/src/theme/tokens/players.ts | 2 +- .../src/types/{styleTree.ts => style_tree.ts} | 0 .../src/utils/{snakeCase.ts => snake_case.ts} | 0 49 files changed, 221 insertions(+), 180 deletions(-) create mode 100644 styles/mod.py rename styles/src/{buildLicenses.ts => build_licenses.ts} (100%) rename styles/src/{buildThemes.ts => build_themes.ts} (90%) rename styles/src/{buildTokens.ts => build_tokens.ts} (92%) rename styles/src/{buildTypes.ts => build_types.ts} (95%) delete mode 100644 styles/src/styleTree/app.ts create mode 100644 styles/src/style_tree/app.ts rename styles/src/{styleTree => style_tree}/assistant.ts (99%) rename styles/src/{styleTree/commandPalette.ts => style_tree/command_palette.ts} (89%) rename styles/src/{styleTree => style_tree}/components.ts (99%) rename styles/src/{styleTree/contactFinder.ts => style_tree/contact_finder.ts} (93%) rename styles/src/{styleTree/contactList.ts => style_tree/contact_list.ts} (98%) rename styles/src/{styleTree/contactNotification.ts => style_tree/contact_notification.ts} (91%) rename styles/src/{styleTree/contactsPopover.ts => style_tree/contacts_popover.ts} (72%) rename styles/src/{styleTree/contextMenu.ts => style_tree/context_menu.ts} (94%) rename styles/src/{styleTree => style_tree}/copilot.ts (99%) rename styles/src/{styleTree => style_tree}/editor.ts (96%) rename styles/src/{styleTree => style_tree}/feedback.ts (96%) rename styles/src/{styleTree/hoverPopover.ts => style_tree/hover_popover.ts} (91%) rename styles/src/{styleTree/incomingCallNotification.ts => style_tree/incoming_call_notification.ts} (93%) rename styles/src/{styleTree => style_tree}/picker.ts (98%) rename styles/src/{styleTree/projectDiagnostics.ts => style_tree/project_diagnostics.ts} (70%) rename styles/src/{styleTree/projectPanel.ts => style_tree/project_panel.ts} (88%) rename styles/src/{styleTree/projectSharedNotification.ts => style_tree/project_shared_notification.ts} (93%) rename styles/src/{styleTree => style_tree}/search.ts (98%) rename styles/src/{styleTree/sharedScreen.ts => style_tree/shared_screen.ts} (84%) rename styles/src/{styleTree/simpleMessageNotification.ts => style_tree/simple_message_notification.ts} (93%) rename styles/src/{styleTree/statusBar.ts => style_tree/status_bar.ts} (97%) rename styles/src/{styleTree/tabBar.ts => style_tree/tab_bar.ts} (96%) rename styles/src/{styleTree => style_tree}/terminal.ts (97%) rename styles/src/{styleTree => style_tree}/titlebar.ts (100%) rename styles/src/{styleTree/toolbarDropdownMenu.ts => style_tree/toolbar_dropdown_menu.ts} (94%) rename styles/src/{styleTree => style_tree}/tooltip.ts (93%) rename styles/src/{styleTree/updateNotification.ts => style_tree/update_notification.ts} (89%) rename styles/src/{styleTree => style_tree}/welcome.ts (98%) rename styles/src/{styleTree => style_tree}/workspace.ts (94%) rename styles/src/theme/{colorScheme.ts => color_scheme.ts} (98%) rename styles/src/theme/{themeConfig.ts => theme_config.ts} (100%) rename styles/src/theme/tokens/{colorScheme.ts => color_scheme.ts} (95%) rename styles/src/types/{styleTree.ts => style_tree.ts} (100%) rename styles/src/utils/{snakeCase.ts => snake_case.ts} (100%) diff --git a/styles/mod.py b/styles/mod.py new file mode 100644 index 0000000000000000000000000000000000000000..77238e5b45428beab2100f6ea2f7be707ae52acd --- /dev/null +++ b/styles/mod.py @@ -0,0 +1,42 @@ +import os, sys, re + + +def camel_to_snake(inputstring): + REG = r'(? { - const id = `${scheme.isLight ? "light" : "dark"}_${scheme.name + const id = `${scheme.is_light ? "light" : "dark"}_${scheme.name .toLowerCase() .replace(/\s+/g, "_")}_${index}` const selectedTokenSets: { [key: string]: "enabled" } = {} @@ -47,7 +47,7 @@ function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] { return { id, - name: `${scheme.name} - ${scheme.isLight ? "Light" : "Dark"}`, + name: `${scheme.name} - ${scheme.is_light ? "Light" : "Dark"}`, selectedTokenSets, } }) diff --git a/styles/src/buildTypes.ts b/styles/src/build_types.ts similarity index 95% rename from styles/src/buildTypes.ts rename to styles/src/build_types.ts index cd788c540ed0172cce7fb07ac538919fc8431187..5957ae5076a08fd62c84822edb946a0ab9a56f0a 100644 --- a/styles/src/buildTypes.ts +++ b/styles/src/build_types.ts @@ -43,7 +43,6 @@ async function main() { return } } catch (e) { - // @ts-expect-error - It's fine if there's no output from a previous run. if (e.code !== "ENOENT") { throw e } diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 39c1adb5e506065207f52d82e919e1aaf55a9bd9..4664928d555dce2a07b2e2d1afb1ed7f3f4fcc6b 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -1,6 +1,6 @@ -import { ColorScheme } from "../common" import { interactive, toggleable } from "../element" -import { background, foreground } from "../styleTree/components" +import { background, foreground } from "../style_tree/components" +import { ColorScheme } from "../theme/color_scheme" export type Margin = { top: number @@ -11,9 +11,9 @@ export type Margin = { interface IconButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial } diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index b8a2272cd340c7785ce172600900e8b2889bc787..64a91de7b0424945e153563d36a012380796add1 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -1,18 +1,18 @@ -import { ColorScheme } from "../common" import { interactive, toggleable } from "../element" import { TextProperties, background, foreground, text, -} from "../styleTree/components" +} from "../style_tree/components" +import { ColorScheme } from "../theme/color_scheme" import { Margin } from "./icon_button" interface TextButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial text_properties?: TextProperties diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts deleted file mode 100644 index a84300283189468dce3de49b430bbf5e848c872c..0000000000000000000000000000000000000000 --- a/styles/src/styleTree/app.ts +++ /dev/null @@ -1,75 +0,0 @@ -import contactFinder from "./contactFinder" -import contactsPopover from "./contactsPopover" -import commandPalette from "./commandPalette" -import editor from "./editor" -import projectPanel from "./projectPanel" -import search from "./search" -import picker from "./picker" -import workspace from "./workspace" -import contextMenu from "./contextMenu" -import sharedScreen from "./sharedScreen" -import projectDiagnostics from "./projectDiagnostics" -import contactNotification from "./contactNotification" -import updateNotification from "./updateNotification" -import simpleMessageNotification from "./simpleMessageNotification" -import projectSharedNotification from "./projectSharedNotification" -import tooltip from "./tooltip" -import terminal from "./terminal" -import contactList from "./contactList" -import toolbarDropdownMenu from "./toolbarDropdownMenu" -import incomingCallNotification from "./incomingCallNotification" -import { ColorScheme } from "../theme/colorScheme" -import feedback from "./feedback" -import welcome from "./welcome" -import copilot from "./copilot" -import assistant from "./assistant" -import { titlebar } from "./titlebar" - -export default function app(colorScheme: ColorScheme): any { - return { - meta: { - name: colorScheme.name, - isLight: colorScheme.isLight, - }, - commandPalette: commandPalette(colorScheme), - contactNotification: contactNotification(colorScheme), - projectSharedNotification: projectSharedNotification(colorScheme), - incomingCallNotification: incomingCallNotification(colorScheme), - picker: picker(colorScheme), - workspace: workspace(colorScheme), - titlebar: titlebar(colorScheme), - copilot: copilot(colorScheme), - welcome: welcome(colorScheme), - contextMenu: contextMenu(colorScheme), - editor: editor(colorScheme), - projectDiagnostics: projectDiagnostics(colorScheme), - projectPanel: projectPanel(colorScheme), - contactsPopover: contactsPopover(colorScheme), - contactFinder: contactFinder(colorScheme), - contactList: contactList(colorScheme), - toolbarDropdownMenu: toolbarDropdownMenu(colorScheme), - search: search(colorScheme), - sharedScreen: sharedScreen(colorScheme), - updateNotification: updateNotification(colorScheme), - simpleMessageNotification: simpleMessageNotification(colorScheme), - tooltip: tooltip(colorScheme), - terminal: terminal(colorScheme), - assistant: assistant(colorScheme), - feedback: feedback(colorScheme), - colorScheme: { - ...colorScheme, - players: Object.values(colorScheme.players), - ramps: { - neutral: colorScheme.ramps.neutral.colors(100, "hex"), - red: colorScheme.ramps.red.colors(100, "hex"), - orange: colorScheme.ramps.orange.colors(100, "hex"), - yellow: colorScheme.ramps.yellow.colors(100, "hex"), - green: colorScheme.ramps.green.colors(100, "hex"), - cyan: colorScheme.ramps.cyan.colors(100, "hex"), - blue: colorScheme.ramps.blue.colors(100, "hex"), - violet: colorScheme.ramps.violet.colors(100, "hex"), - magenta: colorScheme.ramps.magenta.colors(100, "hex"), - }, - }, - } -} diff --git a/styles/src/style_tree/app.ts b/styles/src/style_tree/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..684614d073b495870b1fc06418623587cc69b768 --- /dev/null +++ b/styles/src/style_tree/app.ts @@ -0,0 +1,75 @@ +import contact_finder from "./contact_finder" +import contacts_popover from "./contacts_popover" +import command_palette from "./command_palette" +import project_panel from "./project_panel" +import search from "./search" +import picker from "./picker" +import workspace from "./workspace" +import context_menu from "./context_menu" +import shared_screen from "./shared_screen" +import project_diagnostics from "./project_diagnostics" +import contact_notification from "./contact_notification" +import update_notification from "./update_notification" +import simple_message_notification from "./simple_message_notification" +import project_shared_notification from "./project_shared_notification" +import tooltip from "./tooltip" +import terminal from "./terminal" +import contact_list from "./contact_list" +import toolbar_dropdown_menu from "./toolbar_dropdown_menu" +import incoming_call_notification from "./incoming_call_notification" +import { ColorScheme } from "../theme/color_scheme" +import welcome from "./welcome" +import copilot from "./copilot" +import assistant from "./assistant" +import { titlebar } from "./titlebar" +import editor from "./editor" +import feedback from "./feedback" + +export default function app(theme: ColorScheme): any { + return { + meta: { + name: theme.name, + is_light: theme.is_light, + }, + command_palette: command_palette(theme), + contact_notification: contact_notification(theme), + project_shared_notification: project_shared_notification(theme), + incoming_call_notification: incoming_call_notification(theme), + picker: picker(theme), + workspace: workspace(theme), + titlebar: titlebar(theme), + copilot: copilot(theme), + welcome: welcome(theme), + context_menu: context_menu(theme), + editor: editor(theme), + project_diagnostics: project_diagnostics(theme), + project_panel: project_panel(theme), + contacts_popover: contacts_popover(theme), + contact_finder: contact_finder(theme), + contact_list: contact_list(theme), + toolbar_dropdown_menu: toolbar_dropdown_menu(theme), + search: search(theme), + shared_screen: shared_screen(theme), + update_notification: update_notification(theme), + simple_message_notification: simple_message_notification(theme), + tooltip: tooltip(theme), + terminal: terminal(theme), + assistant: assistant(theme), + feedback: feedback(theme), + color_scheme: { + ...theme, + players: Object.values(theme.players), + ramps: { + neutral: theme.ramps.neutral.colors(100, "hex"), + red: theme.ramps.red.colors(100, "hex"), + orange: theme.ramps.orange.colors(100, "hex"), + yellow: theme.ramps.yellow.colors(100, "hex"), + green: theme.ramps.green.colors(100, "hex"), + cyan: theme.ramps.cyan.colors(100, "hex"), + blue: theme.ramps.blue.colors(100, "hex"), + violet: theme.ramps.violet.colors(100, "hex"), + magenta: theme.ramps.magenta.colors(100, "hex"), + }, + }, + } +} diff --git a/styles/src/styleTree/assistant.ts b/styles/src/style_tree/assistant.ts similarity index 99% rename from styles/src/styleTree/assistant.ts rename to styles/src/style_tree/assistant.ts index f233f0278647b3e143c973f18e1d69eba31e6c0e..7003e0765ce7b72e6766dbbecb8b56ddf3ca8ffc 100644 --- a/styles/src/styleTree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { text, border, background, foreground } from "./components" import editor from "./editor" import { interactive } from "../element" diff --git a/styles/src/styleTree/commandPalette.ts b/styles/src/style_tree/command_palette.ts similarity index 89% rename from styles/src/styleTree/commandPalette.ts rename to styles/src/style_tree/command_palette.ts index 101f4d437e9a9c478ffbf71e36dea40d5f85c527..988a1c949bc468c5df53272b053a8ef72c6739ed 100644 --- a/styles/src/styleTree/commandPalette.ts +++ b/styles/src/style_tree/command_palette.ts @@ -1,9 +1,9 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { text, background } from "./components" import { toggleable } from "../element" -export default function commandPalette(colorScheme: ColorScheme): any { +export default function command_palette(colorScheme: ColorScheme): any { const layer = colorScheme.highest const key = toggleable({ diff --git a/styles/src/styleTree/components.ts b/styles/src/style_tree/components.ts similarity index 99% rename from styles/src/styleTree/components.ts rename to styles/src/style_tree/components.ts index 8db1fa4b2ef5916f6b871bb1b3f1184b15bc7ee5..6f69023d1292c5721e6145f9b3067122c57144c6 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/style_tree/components.ts @@ -1,5 +1,5 @@ import { fontFamilies, fontSizes, FontWeight } from "../common" -import { Layer, Styles, StyleSets, Style } from "../theme/colorScheme" +import { Layer, Styles, StyleSets, Style } from "../theme/color_scheme" function isStyleSet(key: any): key is StyleSets { return [ diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/style_tree/contact_finder.ts similarity index 93% rename from styles/src/styleTree/contactFinder.ts rename to styles/src/style_tree/contact_finder.ts index f0720d05888127c203b800b72199dbcb5829e718..239b4b5004e1a0b0871448f38fb75640f36dad1e 100644 --- a/styles/src/styleTree/contactFinder.ts +++ b/styles/src/style_tree/contact_finder.ts @@ -1,8 +1,8 @@ import picker from "./picker" -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" -export default function contactFinder(colorScheme: ColorScheme): any { +export default function contact_finder(colorScheme: ColorScheme): any { const layer = colorScheme.middle const sideMargin = 6 diff --git a/styles/src/styleTree/contactList.ts b/styles/src/style_tree/contact_list.ts similarity index 98% rename from styles/src/styleTree/contactList.ts rename to styles/src/style_tree/contact_list.ts index 9194e809ee3c24148ed3113a6f5b16fe842019c2..fb6b665a143c65a938111ee6366d775587f04b95 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/style_tree/contact_list.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, borderColor, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function contactsPanel(colorScheme: ColorScheme): any { +export default function contacts_panel(colorScheme: ColorScheme): any { const nameMargin = 8 const sidePadding = 12 diff --git a/styles/src/styleTree/contactNotification.ts b/styles/src/style_tree/contact_notification.ts similarity index 91% rename from styles/src/styleTree/contactNotification.ts rename to styles/src/style_tree/contact_notification.ts index 450f927535ecf7a8ee596e1c32e03acf4d2d9bf4..6ae2dc5d2037f1d1cea868ab6517000d22e8b52e 100644 --- a/styles/src/styleTree/contactNotification.ts +++ b/styles/src/style_tree/contact_notification.ts @@ -1,10 +1,10 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, foreground, text } from "./components" import { interactive } from "../element" const avatarSize = 12 const headerPadding = 8 -export default function contactNotification(colorScheme: ColorScheme): any { +export default function contact_notification(colorScheme: ColorScheme): any { const layer = colorScheme.lowest return { headerAvatar: { diff --git a/styles/src/styleTree/contactsPopover.ts b/styles/src/style_tree/contacts_popover.ts similarity index 72% rename from styles/src/styleTree/contactsPopover.ts rename to styles/src/style_tree/contacts_popover.ts index 6d9f84322d6fe75982d7970e86035f81e2d5c386..0c9d1a47eb4d2d48d6ffc51124b5eea0b2a1d5db 100644 --- a/styles/src/styleTree/contactsPopover.ts +++ b/styles/src/style_tree/contacts_popover.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border } from "./components" -export default function contactsPopover(colorScheme: ColorScheme): any { +export default function contacts_popover(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { background: background(layer), diff --git a/styles/src/styleTree/contextMenu.ts b/styles/src/style_tree/context_menu.ts similarity index 94% rename from styles/src/styleTree/contextMenu.ts rename to styles/src/style_tree/context_menu.ts index 1064eedd0dc79615aede6fa3faed1387ab04e140..52795be79684202c0381ccc4daeb1d868289d31f 100644 --- a/styles/src/styleTree/contextMenu.ts +++ b/styles/src/style_tree/context_menu.ts @@ -1,8 +1,8 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, borderColor, text } from "./components" import { interactive, toggleable } from "../element" -export default function contextMenu(colorScheme: ColorScheme): any { +export default function context_menu(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { background: background(layer), diff --git a/styles/src/styleTree/copilot.ts b/styles/src/style_tree/copilot.ts similarity index 99% rename from styles/src/styleTree/copilot.ts rename to styles/src/style_tree/copilot.ts index 2f82e94c43923740b66e5512022d00d1a2f9d585..cd2bbe0584fd145e3b24270a911e53fae8e7bc89 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/style_tree/copilot.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, svg, text } from "./components" import { interactive } from "../element" export default function copilot(colorScheme: ColorScheme): any { diff --git a/styles/src/styleTree/editor.ts b/styles/src/style_tree/editor.ts similarity index 96% rename from styles/src/styleTree/editor.ts rename to styles/src/style_tree/editor.ts index 71c34207eb91705f5a6d48b67549c8987a6d154d..9bf46055808c75a0da66180066fce46094504db7 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -1,13 +1,13 @@ import { withOpacity } from "../theme/color" -import { ColorScheme, Layer, StyleSets } from "../theme/colorScheme" +import { ColorScheme, Layer, StyleSets } from "../theme/color_scheme" import { background, border, borderColor, foreground, text } from "./components" -import hoverPopover from "./hoverPopover" +import hoverPopover from "./hover_popover" import { buildSyntax } from "../theme/syntax" import { interactive, toggleable } from "../element" export default function editor(colorScheme: ColorScheme): any { - const { isLight } = colorScheme + const { is_light } = colorScheme const layer = colorScheme.highest @@ -130,13 +130,13 @@ export default function editor(colorScheme: ColorScheme): any { foldBackground: foreground(layer, "variant"), }, diff: { - deleted: isLight + deleted: is_light ? colorScheme.ramps.red(0.5).hex() : colorScheme.ramps.red(0.4).hex(), - modified: isLight + modified: is_light ? colorScheme.ramps.yellow(0.5).hex() : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight + inserted: is_light ? colorScheme.ramps.green(0.4).hex() : colorScheme.ramps.green(0.5).hex(), removedWidthEm: 0.275, @@ -292,13 +292,13 @@ export default function editor(colorScheme: ColorScheme): any { }, }, git: { - deleted: isLight + deleted: is_light ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), - modified: isLight + modified: is_light ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), - inserted: isLight + inserted: is_light ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), }, diff --git a/styles/src/styleTree/feedback.ts b/styles/src/style_tree/feedback.ts similarity index 96% rename from styles/src/styleTree/feedback.ts rename to styles/src/style_tree/feedback.ts index 9b66015dc64ee1aa8c42dd005479643100b62ad4..88f3cebc37e7da2ebd5bf33799ee1a392edfd6cd 100644 --- a/styles/src/styleTree/feedback.ts +++ b/styles/src/style_tree/feedback.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" import { interactive } from "../element" diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/style_tree/hover_popover.ts similarity index 91% rename from styles/src/styleTree/hoverPopover.ts rename to styles/src/style_tree/hover_popover.ts index 9e2f8d0a785f1d27744a4e97254e3246aad4a9c6..68eba194948b7217b9db0f7681c5a3dbf90722a9 100644 --- a/styles/src/styleTree/hoverPopover.ts +++ b/styles/src/style_tree/hover_popover.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" -export default function HoverPopover(colorScheme: ColorScheme): any { +export default function hover_popover(colorScheme: ColorScheme): any { const layer = colorScheme.middle const baseContainer = { background: background(layer), diff --git a/styles/src/styleTree/incomingCallNotification.ts b/styles/src/style_tree/incoming_call_notification.ts similarity index 93% rename from styles/src/styleTree/incomingCallNotification.ts rename to styles/src/style_tree/incoming_call_notification.ts index 6249bfb6934ae1c074743b61bd95f3fdc0d99f0f..9a6d400017f734ba5e7bd2a3a35ccf000a466b53 100644 --- a/styles/src/styleTree/incomingCallNotification.ts +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" -export default function incomingCallNotification( +export default function incoming_call_notification( colorScheme: ColorScheme ): unknown { const layer = colorScheme.middle diff --git a/styles/src/styleTree/picker.ts b/styles/src/style_tree/picker.ts similarity index 98% rename from styles/src/styleTree/picker.ts rename to styles/src/style_tree/picker.ts index aaf2740a6e0419aa51b4c61d51bd034edbff210e..417c1faea4c429315a5c439065a77f0672a06eef 100644 --- a/styles/src/styleTree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" diff --git a/styles/src/styleTree/projectDiagnostics.ts b/styles/src/style_tree/project_diagnostics.ts similarity index 70% rename from styles/src/styleTree/projectDiagnostics.ts rename to styles/src/style_tree/project_diagnostics.ts index d2c2152ab416a976ce7ca8098c04b1cf8600af8b..f3f5a5a144653f3573057e19e8c462c553c5f3b7 100644 --- a/styles/src/styleTree/projectDiagnostics.ts +++ b/styles/src/style_tree/project_diagnostics.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, text } from "./components" -export default function projectDiagnostics(colorScheme: ColorScheme): any { +export default function project_diagnostics(colorScheme: ColorScheme): any { const layer = colorScheme.highest return { background: background(layer), diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/style_tree/project_panel.ts similarity index 88% rename from styles/src/styleTree/projectPanel.ts rename to styles/src/style_tree/project_panel.ts index de4a98df53fd1b038e370365d392b8432d72329b..c38c689f4fe3e02801c07d931228bf7c689dbbb4 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { Border, @@ -10,10 +10,10 @@ import { } from "./components" import { interactive, toggleable } from "../element" import merge from "ts-deepmerge" -export default function projectPanel(colorScheme: ColorScheme): any { - const { isLight } = colorScheme +export default function project_panel(theme: ColorScheme): any { + const { is_light } = theme - const layer = colorScheme.middle + const layer = theme.middle type EntryStateProps = { background?: string @@ -31,15 +31,15 @@ export default function projectPanel(colorScheme: ColorScheme): any { const entry = (unselected?: EntryState, selected?: EntryState) => { const git_status = { git: { - modified: isLight - ? colorScheme.ramps.yellow(0.6).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.45).hex() - : colorScheme.ramps.green(0.5).hex(), - conflict: isLight - ? colorScheme.ramps.red(0.6).hex() - : colorScheme.ramps.red(0.5).hex(), + modified: is_light + ? theme.ramps.yellow(0.6).hex() + : theme.ramps.yellow(0.5).hex(), + inserted: is_light + ? theme.ramps.green(0.45).hex() + : theme.ramps.green(0.5).hex(), + conflict: is_light + ? theme.ramps.red(0.6).hex() + : theme.ramps.red(0.5).hex(), }, } @@ -182,7 +182,7 @@ export default function projectPanel(colorScheme: ColorScheme): any { filenameEditor: { background: background(layer, "on"), text: text(layer, "mono", "on", { size: "sm" }), - selection: colorScheme.players[0], + selection: theme.players[0], }, } } diff --git a/styles/src/styleTree/projectSharedNotification.ts b/styles/src/style_tree/project_shared_notification.ts similarity index 93% rename from styles/src/styleTree/projectSharedNotification.ts rename to styles/src/style_tree/project_shared_notification.ts index c114e17176a20cd524552e155df923e2e6c37cc6..f8b103e2e497859445ee82b914b88f80a56a6945 100644 --- a/styles/src/styleTree/projectSharedNotification.ts +++ b/styles/src/style_tree/project_shared_notification.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" -export default function projectSharedNotification( +export default function project_shared_notification( colorScheme: ColorScheme ): unknown { const layer = colorScheme.middle diff --git a/styles/src/styleTree/search.ts b/styles/src/style_tree/search.ts similarity index 98% rename from styles/src/styleTree/search.ts rename to styles/src/style_tree/search.ts index 9ecad3ab1973f61c11439f9c50c1f4093c022fe4..c236284fa06ed1f7839ea7cefcaf5a842aac7468 100644 --- a/styles/src/styleTree/search.ts +++ b/styles/src/style_tree/search.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" diff --git a/styles/src/styleTree/sharedScreen.ts b/styles/src/style_tree/shared_screen.ts similarity index 84% rename from styles/src/styleTree/sharedScreen.ts rename to styles/src/style_tree/shared_screen.ts index 59968d59499c10cf03904bebb7270e47036dab11..718ea20642dd401f14d1f35c80c87921b3349b44 100644 --- a/styles/src/styleTree/sharedScreen.ts +++ b/styles/src/style_tree/shared_screen.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { StyleTree } from "../types" import { background } from "./components" diff --git a/styles/src/styleTree/simpleMessageNotification.ts b/styles/src/style_tree/simple_message_notification.ts similarity index 93% rename from styles/src/styleTree/simpleMessageNotification.ts rename to styles/src/style_tree/simple_message_notification.ts index 99dc878a79a4b44c54c5e9a90a3647c4a922523c..207924a99fbafddec7b4b3e78e03ea60d1e8a58d 100644 --- a/styles/src/styleTree/simpleMessageNotification.ts +++ b/styles/src/style_tree/simple_message_notification.ts @@ -1,10 +1,10 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive } from "../element" const headerPadding = 8 -export default function simpleMessageNotification( +export default function simple_message_notification( colorScheme: ColorScheme ): unknown { const layer = colorScheme.middle diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/style_tree/status_bar.ts similarity index 97% rename from styles/src/styleTree/statusBar.ts rename to styles/src/style_tree/status_bar.ts index 6ca53afb187b866e4a3a5bfa561db992845538c0..e86afb1c0da2acd81d8a4f6635bcb3555e78b48e 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function statusBar(colorScheme: ColorScheme): any { +export default function status_bar(colorScheme: ColorScheme): any { const layer = colorScheme.lowest const statusContainer = { diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/style_tree/tab_bar.ts similarity index 96% rename from styles/src/styleTree/tabBar.ts rename to styles/src/style_tree/tab_bar.ts index ae9512d8ce57a50482892c22d860bcb7a5db7084..fe43934f79fa4a491009355f95249f5d6ad71ec7 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -1,9 +1,9 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { text, border, background, foreground } from "./components" import { interactive, toggleable } from "../element" -export default function tabBar(colorScheme: ColorScheme): any { +export default function tab_bar(colorScheme: ColorScheme): any { const height = 32 const activeLayer = colorScheme.highest diff --git a/styles/src/styleTree/terminal.ts b/styles/src/style_tree/terminal.ts similarity index 97% rename from styles/src/styleTree/terminal.ts rename to styles/src/style_tree/terminal.ts index 5198cb195dd30b78130c917e09798392ca56a393..4e3dc18627c3262b78acc402e67352100e7aa2dc 100644 --- a/styles/src/styleTree/terminal.ts +++ b/styles/src/style_tree/terminal.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { StyleTree } from "../types" export default function terminal(theme: ColorScheme): StyleTree.TerminalStyle { diff --git a/styles/src/styleTree/titlebar.ts b/styles/src/style_tree/titlebar.ts similarity index 100% rename from styles/src/styleTree/titlebar.ts rename to styles/src/style_tree/titlebar.ts diff --git a/styles/src/styleTree/toolbarDropdownMenu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts similarity index 94% rename from styles/src/styleTree/toolbarDropdownMenu.ts rename to styles/src/style_tree/toolbar_dropdown_menu.ts index 4eff06026b0551e3671578ca644481eaf2135f46..992631e0363eb668695d3ebaa2633e0c5a2bd077 100644 --- a/styles/src/styleTree/toolbarDropdownMenu.ts +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -1,7 +1,7 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" -export default function dropdownMenu(colorScheme: ColorScheme): any { +export default function dropdown_menu(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { diff --git a/styles/src/styleTree/tooltip.ts b/styles/src/style_tree/tooltip.ts similarity index 93% rename from styles/src/styleTree/tooltip.ts rename to styles/src/style_tree/tooltip.ts index fb896112a9990059c90e2785ee475999f478b11a..54daae529e55782cc11f659a943461ca92ab3b9c 100644 --- a/styles/src/styleTree/tooltip.ts +++ b/styles/src/style_tree/tooltip.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" export default function tooltip(colorScheme: ColorScheme): any { diff --git a/styles/src/styleTree/updateNotification.ts b/styles/src/style_tree/update_notification.ts similarity index 89% rename from styles/src/styleTree/updateNotification.ts rename to styles/src/style_tree/update_notification.ts index bf792ffc7b5bfa04b948b8fa4d66264d33b53b51..c6e6130f540c1b7fc448dce99ff7048bdd2d71f5 100644 --- a/styles/src/styleTree/updateNotification.ts +++ b/styles/src/style_tree/update_notification.ts @@ -1,10 +1,10 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { foreground, text } from "./components" import { interactive } from "../element" const headerPadding = 8 -export default function updateNotification(colorScheme: ColorScheme): any { +export default function update_notification(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { message: { diff --git a/styles/src/styleTree/welcome.ts b/styles/src/style_tree/welcome.ts similarity index 98% rename from styles/src/styleTree/welcome.ts rename to styles/src/style_tree/welcome.ts index c64a17b5f60ec194ac167710cd845aafb8da2296..93f18267401da2a5abc9b39defbc505824b634ab 100644 --- a/styles/src/styleTree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { border, diff --git a/styles/src/styleTree/workspace.ts b/styles/src/style_tree/workspace.ts similarity index 94% rename from styles/src/styleTree/workspace.ts rename to styles/src/style_tree/workspace.ts index 5f5da7e47ea5ccadfa41189f155813a83b9adc3f..48217e89c74234f324c9788142e8721e57004b46 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -1,4 +1,4 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { withOpacity } from "../theme/color" import { background, @@ -8,14 +8,14 @@ import { svg, text, } from "./components" -import statusBar from "./statusBar" -import tabBar from "./tabBar" +import statusBar from "./status_bar" +import tabBar from "./tab_bar" import { interactive } from "../element" import { titlebar } from "./titlebar" export default function workspace(colorScheme: ColorScheme): any { const layer = colorScheme.lowest - const isLight = colorScheme.isLight + const is_light = colorScheme.is_light return { background: background(colorScheme.lowest), @@ -25,7 +25,7 @@ export default function workspace(colorScheme: ColorScheme): any { height: 256, }, logo: svg( - withOpacity("#000000", colorScheme.isLight ? 0.6 : 0.8), + withOpacity("#000000", colorScheme.is_light ? 0.6 : 0.8), "icons/logo_96.svg", 256, 256 @@ -33,10 +33,10 @@ export default function workspace(colorScheme: ColorScheme): any { logoShadow: svg( withOpacity( - colorScheme.isLight + colorScheme.is_light ? "#FFFFFF" : colorScheme.lowest.base.default.background, - colorScheme.isLight ? 1 : 0.6 + colorScheme.is_light ? 1 : 0.6 ), "icons/logo_96.svg", 256, @@ -96,7 +96,7 @@ export default function workspace(colorScheme: ColorScheme): any { }, zoomedBackground: { cursor: "Arrow", - background: isLight + background: is_light ? withOpacity(background(colorScheme.lowest), 0.8) : withOpacity(background(colorScheme.highest), 0.6), }, diff --git a/styles/src/theme/colorScheme.ts b/styles/src/theme/color_scheme.ts similarity index 98% rename from styles/src/theme/colorScheme.ts rename to styles/src/theme/color_scheme.ts index ea3b1b9b2980be2651258f4f064658b28f13efca..61b459911b96aec679ae58b57bfc477470344483 100644 --- a/styles/src/theme/colorScheme.ts +++ b/styles/src/theme/color_scheme.ts @@ -5,12 +5,12 @@ import { ThemeConfig, ThemeAppearance, ThemeConfigInputColors, -} from "./themeConfig" +} from "./theme_config" import { getRamps } from "./ramps" export interface ColorScheme { name: string - isLight: boolean + is_light: boolean lowest: Layer middle: Layer @@ -155,7 +155,7 @@ export function createColorScheme(theme: ThemeConfig): ColorScheme { return { name, - isLight, + is_light: isLight, ramps, diff --git a/styles/src/theme/index.ts b/styles/src/theme/index.ts index 2bf625521c19711178278d28feb88e0bba86f773..22287bf6699a7f690b1b6b29b0689adc47dea757 100644 --- a/styles/src/theme/index.ts +++ b/styles/src/theme/index.ts @@ -1,4 +1,4 @@ -export * from "./colorScheme" +export * from "./color_scheme" export * from "./ramps" export * from "./syntax" -export * from "./themeConfig" +export * from "./theme_config" diff --git a/styles/src/theme/ramps.ts b/styles/src/theme/ramps.ts index de1f8ee0d4ca6bb33853ef65fa67eb16f715a9ce..98a73ef5bf0c768b08159ad9bca44ea19510a6b6 100644 --- a/styles/src/theme/ramps.ts +++ b/styles/src/theme/ramps.ts @@ -1,9 +1,9 @@ import chroma, { Color, Scale } from "chroma-js" -import { RampSet } from "./colorScheme" +import { RampSet } from "./color_scheme" import { ThemeConfigInputColors, ThemeConfigInputColorsKeys, -} from "./themeConfig" +} from "./theme_config" export function colorRamp(color: Color): Scale { const endColor = color.desaturate(1).brighten(5) diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index a35013c1e9a941cd07cefa7c8d990213e8df1b8c..5d214abdebe5a93cdf1d25e7b0ff31168fe5cdd2 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -1,6 +1,6 @@ import deepmerge from "deepmerge" import { FontWeight, fontWeights } from "../common" -import { ColorScheme } from "./colorScheme" +import { ColorScheme } from "./color_scheme" import chroma from "chroma-js" export interface SyntaxHighlightStyle { diff --git a/styles/src/theme/themeConfig.ts b/styles/src/theme/theme_config.ts similarity index 100% rename from styles/src/theme/themeConfig.ts rename to styles/src/theme/theme_config.ts diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/color_scheme.ts similarity index 95% rename from styles/src/theme/tokens/colorScheme.ts rename to styles/src/theme/tokens/color_scheme.ts index bc53ca802a9a4f5cf39dd3345734674558170494..84e456a6e7f223d728fe6cee3e73b0bfa3b155bc 100644 --- a/styles/src/theme/tokens/colorScheme.ts +++ b/styles/src/theme/tokens/color_scheme.ts @@ -9,12 +9,12 @@ import { Shadow, SyntaxHighlightStyle, ThemeSyntax, -} from "../colorScheme" +} from "../color_scheme" import { LayerToken, layerToken } from "./layer" import { PlayersToken, playersToken } from "./players" import { colorToken } from "./token" import { Syntax } from "../syntax" -import editor from "../../styleTree/editor" +import editor from "../../style_tree/editor" interface ColorSchemeTokens { name: SingleOtherToken @@ -85,7 +85,7 @@ export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { }, appearance: { name: "themeAppearance", - value: colorScheme.isLight ? "light" : "dark", + value: colorScheme.is_light ? "light" : "dark", type: TokenTypes.OTHER, }, lowest: layerToken(colorScheme.lowest, "lowest"), diff --git a/styles/src/theme/tokens/layer.ts b/styles/src/theme/tokens/layer.ts index 42a69b5a528161fac21fbb88f6778933772a32a4..10309e0d2e2bdda2f3c938ee20b1aa43d9aee4e0 100644 --- a/styles/src/theme/tokens/layer.ts +++ b/styles/src/theme/tokens/layer.ts @@ -1,5 +1,5 @@ import { SingleColorToken } from "@tokens-studio/types" -import { Layer, Style, StyleSet } from "../colorScheme" +import { Layer, Style, StyleSet } from "../color_scheme" import { colorToken } from "./token" interface StyleToken { diff --git a/styles/src/theme/tokens/players.ts b/styles/src/theme/tokens/players.ts index 94d05cd827dbf8443d3f57e9008aa49da1333c8a..12f16343e9d08e670cb58ebe24c847a5c10786cf 100644 --- a/styles/src/theme/tokens/players.ts +++ b/styles/src/theme/tokens/players.ts @@ -1,6 +1,6 @@ import { SingleColorToken } from "@tokens-studio/types" -import { ColorScheme, Players } from "../../common" import { colorToken } from "./token" +import { ColorScheme, Players } from "../color_scheme" export type PlayerToken = Record<"selection" | "cursor", SingleColorToken> diff --git a/styles/src/types/styleTree.ts b/styles/src/types/style_tree.ts similarity index 100% rename from styles/src/types/styleTree.ts rename to styles/src/types/style_tree.ts diff --git a/styles/src/utils/snakeCase.ts b/styles/src/utils/snake_case.ts similarity index 100% rename from styles/src/utils/snakeCase.ts rename to styles/src/utils/snake_case.ts From e0d618862c410741b65200ed3e7058184bef58d3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 28 Jun 2023 16:23:07 -0700 Subject: [PATCH 028/169] Add click out handler Make all context menus on button click toggles instead of re-shows --- crates/context_menu/src/context_menu.rs | 31 ++++++++----------- crates/gpui/src/app/window.rs | 2 +- .../gpui/src/elements/mouse_event_handler.rs | 12 ++++++- crates/gpui/src/scene/mouse_region.rs | 6 +++- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 9bdf146da47e4f8e882cabbf0fd116b7f5676b7f..a603b3578adb7aa86e657a327b35a4c2c40128cc 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -301,23 +301,18 @@ impl ContextMenu { cx: &mut ViewContext, ) { let mut items = items.into_iter().peekable(); - dbg!(self.visible); - if (self.visible) { - self.visible = false; - } else { - if items.peek().is_some() { - self.items = items.collect(); - self.anchor_position = anchor_position; - self.anchor_corner = anchor_corner; - self.visible = true; - self.show_count += 1; - if !cx.is_self_focused() { - self.previously_focused_view_id = cx.focused_view_id(); - } - cx.focus_self(); - } else { - self.visible = false; + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !cx.is_self_focused() { + self.previously_focused_view_id = cx.focused_view_id(); } + cx.focus_self(); + } else { + self.visible = false; } cx.notify(); } @@ -482,10 +477,10 @@ impl ContextMenu { .contained() .with_style(style.container) }) - .on_down_out(MouseButton::Left, |_, this, cx| { + .on_click_out(MouseButton::Left, |_, this, cx| { this.cancel(&Default::default(), cx); }) - .on_down_out(MouseButton::Right, |_, this, cx| { + .on_click_out(MouseButton::Right, |_, this, cx| { this.cancel(&Default::default(), cx); }) } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index cc6778c930c0fb7b8740738ca0c202be8a4d8fd2..5b15e62efac88815b79305560e842e6671b38fd8 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -716,7 +716,7 @@ impl<'a> WindowContext<'a> { } } - MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) | MouseEvent::ClickOut(_) => { for (mouse_region, _) in self.window.mouse_regions.iter().rev() { // NOT contains if !mouse_region diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 6f2762db66144f1099bd05cee01be43f399b0141..03f481b071af9d9f8d09f01c60a4e1478770ebe9 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -8,7 +8,7 @@ use crate::{ platform::MouseButton, scene::{ CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, + MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, MouseClickOut, }, AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View, ViewContext, @@ -136,6 +136,16 @@ impl MouseEventHandler { self } + pub fn on_click_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_click_out(button, handler); + self + } + + pub fn on_down_out( mut self, button: MouseButton, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 7e1d5d6e1e4f12c0162607d3c4460fe37092a348..3576529eecd4ba50321bb51660346f8246a6adc0 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -94,7 +94,7 @@ impl MouseRegion { V: View, F: Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, { - self.handlers = self.handlers.on_click(button, handler); + self.handlers = self.handlers.on_click_out(button, handler); self } @@ -255,6 +255,10 @@ impl HandlerSet { HandlerKey::new(MouseEvent::click_disc(), Some(button)), SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), ); + set.insert( + HandlerKey::new(MouseEvent::click_out_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), + ); set.insert( HandlerKey::new(MouseEvent::down_out_disc(), Some(button)), SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), From 6ffa6afd20c693dadcc0cd5ca733c809cf465e7e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 28 Jun 2023 16:35:57 -0700 Subject: [PATCH 029/169] fmt --- crates/gpui/src/app/window.rs | 9 ++++++--- crates/gpui/src/elements/mouse_event_handler.rs | 5 ++--- crates/gpui/src/scene/mouse_region.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 5b15e62efac88815b79305560e842e6671b38fd8..23fbb33fe142fc8c34eee86a7889fbeddd9bcc5a 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -8,8 +8,8 @@ use crate::{ MouseButton, MouseMovedEvent, PromptLevel, WindowBounds, }, scene::{ - CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, MouseClickOut, + CursorRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseEvent, + MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, }, text_layout::TextLayoutCache, util::post_inc, @@ -716,7 +716,10 @@ impl<'a> WindowContext<'a> { } } - MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) | MouseEvent::ClickOut(_) => { + MouseEvent::MoveOut(_) + | MouseEvent::UpOut(_) + | MouseEvent::DownOut(_) + | MouseEvent::ClickOut(_) => { for (mouse_region, _) in self.window.mouse_regions.iter().rev() { // NOT contains if !mouse_region diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 03f481b071af9d9f8d09f01c60a4e1478770ebe9..1b8142d96464ff7c9f30230510a13c58c6f08cb6 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,8 +7,8 @@ use crate::{ platform::CursorStyle, platform::MouseButton, scene::{ - CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, MouseClickOut, + CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, + MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View, ViewContext, @@ -145,7 +145,6 @@ impl MouseEventHandler { self } - pub fn on_down_out( mut self, button: MouseButton, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 3576529eecd4ba50321bb51660346f8246a6adc0..ca2cc04b9d1cf75c54ee9593705172f93c6f8ec4 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -14,7 +14,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseMoveOut, MouseScrollWheel, MouseClickOut, + MouseClickOut, MouseMoveOut, MouseScrollWheel, }; #[derive(Clone)] From b015f506da9a2c684f453c43ccbb92144162db5f Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 28 Jun 2023 22:42:57 -0400 Subject: [PATCH 030/169] WIP snake_case 1/? through `contact_notification` --- styles/src/component/icon_button.ts | 6 +- styles/src/component/text_button.ts | 6 +- styles/src/element/interactive.ts | 40 +++--- styles/src/element/toggle.ts | 8 +- styles/src/style_tree/assistant.ts | 44 +++--- styles/src/style_tree/command_palette.ts | 4 +- styles/src/style_tree/components.ts | 130 +++++++++--------- styles/src/style_tree/contact_finder.ts | 56 ++++---- styles/src/style_tree/contact_list.ts | 85 ++++++------ styles/src/style_tree/contact_notification.ts | 35 +++-- styles/src/style_tree/contacts_popover.ts | 2 +- styles/src/style_tree/context_menu.ts | 10 +- styles/src/style_tree/copilot.ts | 8 +- styles/src/style_tree/editor.ts | 28 ++-- styles/src/style_tree/feedback.ts | 2 +- styles/src/style_tree/hover_popover.ts | 2 +- .../style_tree/incoming_call_notification.ts | 4 +- styles/src/style_tree/picker.ts | 4 +- styles/src/style_tree/project_diagnostics.ts | 2 +- styles/src/style_tree/project_panel.ts | 2 +- .../style_tree/project_shared_notification.ts | 4 +- styles/src/style_tree/search.ts | 8 +- styles/src/style_tree/shared_screen.ts | 5 +- .../style_tree/simple_message_notification.ts | 6 +- styles/src/style_tree/status_bar.ts | 12 +- styles/src/style_tree/tab_bar.ts | 8 +- styles/src/style_tree/terminal.ts | 3 +- styles/src/style_tree/titlebar.ts | 12 +- .../src/style_tree/toolbar_dropdown_menu.ts | 2 +- styles/src/style_tree/tooltip.ts | 4 +- styles/src/style_tree/update_notification.ts | 4 +- styles/src/style_tree/welcome.ts | 6 +- styles/src/style_tree/workspace.ts | 22 +-- styles/src/theme/color_scheme.ts | 6 +- styles/src/types/element.ts | 4 - styles/src/types/index.ts | 5 - styles/src/types/property.ts | 9 -- styles/src/types/style_tree.ts | 33 ----- styles/src/types/util.ts | 17 --- styles/src/utils/snake_case.ts | 4 +- styles/tsconfig.json | 36 ++--- 41 files changed, 302 insertions(+), 386 deletions(-) delete mode 100644 styles/src/types/element.ts delete mode 100644 styles/src/types/index.ts delete mode 100644 styles/src/types/property.ts delete mode 100644 styles/src/types/style_tree.ts delete mode 100644 styles/src/types/util.ts diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 4664928d555dce2a07b2e2d1afb1ed7f3f4fcc6b..79891c2477809a1312c968241ee833b62f0922cd 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -11,9 +11,9 @@ export type Margin = { interface IconButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial } diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index 64a91de7b0424945e153563d36a012380796add1..477c2515e320d5e1d4ac2b2cdaf7f8dd587cdde8 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -10,9 +10,9 @@ import { Margin } from "./icon_button" interface TextButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial text_properties?: TextProperties diff --git a/styles/src/element/interactive.ts b/styles/src/element/interactive.ts index 99b8996aefd7521398880bc7ebeb375a3cb123e3..59ccff40f7c71d0997eb4c112c89d1e316096f32 100644 --- a/styles/src/element/interactive.ts +++ b/styles/src/element/interactive.ts @@ -43,55 +43,55 @@ export function interactive({ }: InteractiveProps): Interactive { if (!base && !state.default) throw new Error(NO_DEFAULT_OR_BASE_ERROR) - let defaultState: T + let default_state: T if (state.default && base) { - defaultState = merge(base, state.default) as T + default_state = merge(base, state.default) as T } else { - defaultState = base ? base : (state.default as T) + default_state = base ? base : (state.default as T) } - const interactiveObj: Interactive = { - default: defaultState, + const interactive_obj: Interactive = { + default: default_state, } - let stateCount = 0 + let state_count = 0 if (state.hovered !== undefined) { - interactiveObj.hovered = merge( - interactiveObj.default, + interactive_obj.hovered = merge( + interactive_obj.default, state.hovered ) as T - stateCount++ + state_count++ } if (state.clicked !== undefined) { - interactiveObj.clicked = merge( - interactiveObj.default, + interactive_obj.clicked = merge( + interactive_obj.default, state.clicked ) as T - stateCount++ + state_count++ } if (state.selected !== undefined) { - interactiveObj.selected = merge( - interactiveObj.default, + interactive_obj.selected = merge( + interactive_obj.default, state.selected ) as T - stateCount++ + state_count++ } if (state.disabled !== undefined) { - interactiveObj.disabled = merge( - interactiveObj.default, + interactive_obj.disabled = merge( + interactive_obj.default, state.disabled ) as T - stateCount++ + state_count++ } - if (stateCount < 1) { + if (state_count < 1) { throw new Error(NOT_ENOUGH_STATES_ERROR) } - return interactiveObj + return interactive_obj } diff --git a/styles/src/element/toggle.ts b/styles/src/element/toggle.ts index ead8f1e8248d16241706d2098067a913d8737917..c3cde46d65962e90e966435c9bc07d5d4d023d93 100644 --- a/styles/src/element/toggle.ts +++ b/styles/src/element/toggle.ts @@ -35,13 +35,13 @@ export function toggleable( if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR) if (!state.active) throw new Error(NO_ACTIVE_ERROR) - const inactiveState = base + const inactive_state = base ? ((state.inactive ? merge(base, state.inactive) : base) as T) : (state.inactive as T) - const toggleObj: Toggleable = { - inactive: inactiveState, + const toggle_obj: Toggleable = { + inactive: inactive_state, active: merge(base ?? {}, state.active) as T, } - return toggleObj + return toggle_obj } diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7003e0765ce7b72e6766dbbecb8b56ddf3ca8ffc..fbbfbc4cf1cb5f0a487830247294b692dbe96434 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -10,11 +10,11 @@ export default function assistant(colorScheme: ColorScheme): any { background: editor(colorScheme).background, padding: { left: 12 }, }, - messageHeader: { + message_header: { margin: { bottom: 6, top: 6 }, background: editor(colorScheme).background, }, - hamburgerButton: interactive({ + hamburger_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -36,7 +36,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - splitButton: interactive({ + split_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -58,7 +58,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - quoteButton: interactive({ + quote_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -80,7 +80,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - assistButton: interactive({ + assist_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -102,7 +102,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - zoomInButton: interactive({ + zoom_in_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -124,7 +124,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - zoomOutButton: interactive({ + zoom_out_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -146,7 +146,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - plusButton: interactive({ + plus_button: interactive({ base: { icon: { color: foreground(layer, "variant"), @@ -171,7 +171,7 @@ export default function assistant(colorScheme: ColorScheme): any { title: { ...text(layer, "sans", "default", { size: "sm" }), }, - savedConversation: { + saved_conversation: { container: interactive({ base: { background: background(layer, "on"), @@ -195,7 +195,7 @@ export default function assistant(colorScheme: ColorScheme): any { }), }, }, - userSender: { + user_sender: { default: { ...text(layer, "sans", "default", { size: "sm", @@ -203,7 +203,7 @@ export default function assistant(colorScheme: ColorScheme): any { }), }, }, - assistantSender: { + assistant_sender: { default: { ...text(layer, "sans", "accent", { size: "sm", @@ -211,7 +211,7 @@ export default function assistant(colorScheme: ColorScheme): any { }), }, }, - systemSender: { + system_sender: { default: { ...text(layer, "sans", "variant", { size: "sm", @@ -219,7 +219,7 @@ export default function assistant(colorScheme: ColorScheme): any { }), }, }, - sentAt: { + sent_at: { margin: { top: 2, left: 8 }, ...text(layer, "sans", "default", { size: "2xs" }), }, @@ -228,7 +228,7 @@ export default function assistant(colorScheme: ColorScheme): any { background: background(layer, "on"), margin: { left: 12, right: 12, top: 12 }, padding: 4, - cornerRadius: 4, + corner_radius: 4, ...text(layer, "sans", "default", { size: "xs" }), }, state: { @@ -238,28 +238,28 @@ export default function assistant(colorScheme: ColorScheme): any { }, }, }), - remainingTokens: { + remaining_tokens: { background: background(layer, "on"), margin: { top: 12, right: 24 }, padding: 4, - cornerRadius: 4, + corner_radius: 4, ...text(layer, "sans", "positive", { size: "xs" }), }, - noRemainingTokens: { + no_remaining_tokens: { background: background(layer, "on"), margin: { top: 12, right: 24 }, padding: 4, - cornerRadius: 4, + corner_radius: 4, ...text(layer, "sans", "negative", { size: "xs" }), }, - errorIcon: { + error_icon: { margin: { left: 8 }, color: foreground(layer, "negative"), width: 12, }, - apiKeyEditor: { + api_key_editor: { background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, text: text(layer, "mono", "on"), placeholderText: text(layer, "mono", "on", "disabled", { size: "xs", @@ -273,7 +273,7 @@ export default function assistant(colorScheme: ColorScheme): any { top: 4, }, }, - apiKeyPrompt: { + api_key_prompt: { padding: 10, ...text(layer, "sans", "default", { size: "xs" }), }, diff --git a/styles/src/style_tree/command_palette.ts b/styles/src/style_tree/command_palette.ts index 988a1c949bc468c5df53272b053a8ef72c6739ed..9198f87299dcd7deab443556416296e667175683 100644 --- a/styles/src/style_tree/command_palette.ts +++ b/styles/src/style_tree/command_palette.ts @@ -9,7 +9,7 @@ export default function command_palette(colorScheme: ColorScheme): any { const key = toggleable({ base: { text: text(layer, "mono", "variant", "default", { size: "xs" }), - cornerRadius: 2, + corner_radius: 2, background: background(layer, "on"), padding: { top: 1, @@ -32,7 +32,7 @@ export default function command_palette(colorScheme: ColorScheme): any { }) return { - keystrokeSpacing: 8, + keystroke_spacing: 8, // TODO: This should be a Toggle on the rust side so we don't have to do this key: { inactive: { ...key.inactive }, diff --git a/styles/src/style_tree/components.ts b/styles/src/style_tree/components.ts index 6f69023d1292c5721e6145f9b3067122c57144c6..c0b8e9462fb71c69c77f14a16139564efb79dd94 100644 --- a/styles/src/style_tree/components.ts +++ b/styles/src/style_tree/components.ts @@ -1,7 +1,11 @@ -import { fontFamilies, fontSizes, FontWeight } from "../common" +import { + fontFamilies as font_families, + fontSizes as font_sizes, + FontWeight, +} from "../common" import { Layer, Styles, StyleSets, Style } from "../theme/color_scheme" -function isStyleSet(key: any): key is StyleSets { +function is_style_set(key: any): key is StyleSets { return [ "base", "variant", @@ -13,7 +17,7 @@ function isStyleSet(key: any): key is StyleSets { ].includes(key) } -function isStyle(key: any): key is Styles { +function is_style(key: any): key is Styles { return [ "default", "active", @@ -23,78 +27,70 @@ function isStyle(key: any): key is Styles { "inverted", ].includes(key) } -function getStyle( +function get_style( layer: Layer, - possibleStyleSetOrStyle?: any, - possibleStyle?: any + possible_style_set_or_style?: any, + possible_style?: any ): Style { - let styleSet: StyleSets = "base" + let style_set: StyleSets = "base" let style: Styles = "default" - if (isStyleSet(possibleStyleSetOrStyle)) { - styleSet = possibleStyleSetOrStyle - } else if (isStyle(possibleStyleSetOrStyle)) { - style = possibleStyleSetOrStyle + if (is_style_set(possible_style_set_or_style)) { + style_set = possible_style_set_or_style + } else if (is_style(possible_style_set_or_style)) { + style = possible_style_set_or_style } - if (isStyle(possibleStyle)) { - style = possibleStyle + if (is_style(possible_style)) { + style = possible_style } - return layer[styleSet][style] + return layer[style_set][style] } export function background(layer: Layer, style?: Styles): string export function background( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string export function background( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).background + return get_style(layer, style_set_or_styles, style).background } -export function borderColor(layer: Layer, style?: Styles): string -export function borderColor( +export function border_color(layer: Layer, style?: Styles): string +export function border_color( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string -export function borderColor( +export function border_color( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).border + return get_style(layer, style_set_or_styles, style).border } export function foreground(layer: Layer, style?: Styles): string export function foreground( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string export function foreground( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).foreground -} - -interface Text extends Object { - family: keyof typeof fontFamilies - color: string - size: number - weight?: FontWeight - underline?: boolean + return get_style(layer, style_set_or_styles, style).foreground } export interface TextStyle extends Object { - family: keyof typeof fontFamilies + family: keyof typeof font_families color: string size: number weight?: FontWeight @@ -102,7 +98,7 @@ export interface TextStyle extends Object { } export interface TextProperties { - size?: keyof typeof fontSizes + size?: keyof typeof font_sizes weight?: FontWeight underline?: boolean color?: string @@ -182,49 +178,53 @@ interface FontFeatures { export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, + font_family: keyof typeof font_families, + style_set: StyleSets, style: Styles, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, + font_family: keyof typeof font_families, + style_set: StyleSets, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, + font_family: keyof typeof font_families, style: Styles, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, + font_family: keyof typeof font_families, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSetStyleOrProperties?: StyleSets | Styles | TextProperties, - styleOrProperties?: Styles | TextProperties, + font_family: keyof typeof font_families, + style_set_style_or_properties?: StyleSets | Styles | TextProperties, + style_or_properties?: Styles | TextProperties, properties?: TextProperties ) { - const style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = get_style( + layer, + style_set_style_or_properties, + style_or_properties + ) - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties + if (typeof style_set_style_or_properties === "object") { + properties = style_set_style_or_properties } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties + if (typeof style_or_properties === "object") { + properties = style_or_properties } - const size = fontSizes[properties?.size || "sm"] + const size = font_sizes[properties?.size || "sm"] const color = properties?.color || style.foreground return { - family: fontFamilies[fontFamily], + family: font_families[font_family], ...properties, color, size, @@ -252,13 +252,13 @@ export interface BorderProperties { export function border( layer: Layer, - styleSet: StyleSets, + style_set: StyleSets, style: Styles, properties?: BorderProperties ): Border export function border( layer: Layer, - styleSet: StyleSets, + style_set: StyleSets, properties?: BorderProperties ): Border export function border( @@ -269,17 +269,17 @@ export function border( export function border(layer: Layer, properties?: BorderProperties): Border export function border( layer: Layer, - styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties, - styleOrProperties?: Styles | BorderProperties, + style_set_or_properties?: StyleSets | Styles | BorderProperties, + style_or_properties?: Styles | BorderProperties, properties?: BorderProperties ): Border { - const style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = get_style(layer, style_set_or_properties, style_or_properties) - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties + if (typeof style_set_or_properties === "object") { + properties = style_set_or_properties } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties + if (typeof style_or_properties === "object") { + properties = style_or_properties } return { diff --git a/styles/src/style_tree/contact_finder.ts b/styles/src/style_tree/contact_finder.ts index 239b4b5004e1a0b0871448f38fb75640f36dad1e..f68ce06d3546112bf8979365ac5a72e6843aa5c1 100644 --- a/styles/src/style_tree/contact_finder.ts +++ b/styles/src/style_tree/contact_finder.ts @@ -2,25 +2,25 @@ import picker from "./picker" import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" -export default function contact_finder(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function contact_finder(theme: ColorScheme): any { + const layer = theme.middle - const sideMargin = 6 - const contactButton = { + const side_margin = 6 + const contact_button = { background: background(layer, "variant"), color: foreground(layer, "variant"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, + icon_width: 8, + button_width: 16, + corner_radius: 8, } - const pickerStyle = picker(colorScheme) - const pickerInput = { + const picker_style = picker(theme) + const picker_input = { background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, text: text(layer, "mono"), - placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), - selection: colorScheme.players[0], + placeholder_text: text(layer, "mono", "on", "disabled", { size: "xs" }), + selection: theme.players[0], border: border(layer), padding: { bottom: 4, @@ -29,40 +29,40 @@ export default function contact_finder(colorScheme: ColorScheme): any { top: 4, }, margin: { - left: sideMargin, - right: sideMargin, + left: side_margin, + right: side_margin, }, } return { picker: { - emptyContainer: {}, + empty_container: {}, item: { - ...pickerStyle.item, - margin: { left: sideMargin, right: sideMargin }, + ...picker_style.item, + margin: { left: side_margin, right: side_margin }, }, - noMatches: pickerStyle.noMatches, - inputEditor: pickerInput, - emptyInputEditor: pickerInput, + no_matches: picker_style.noMatches, + input_editor: picker_input, + empty_input_editor: picker_input, }, - rowHeight: 28, - contactAvatar: { - cornerRadius: 10, + row_height: 28, + contact_avatar: { + corner_radius: 10, width: 18, }, - contactUsername: { + contact_username: { padding: { left: 8, }, }, - contactButton: { - ...contactButton, + contact_button: { + ...contact_button, hover: { background: background(layer, "variant", "hovered"), }, }, - disabledContactButton: { - ...contactButton, + disabled_contact_button: { + ...contact_button, background: background(layer, "disabled"), color: foreground(layer, "disabled"), }, diff --git a/styles/src/style_tree/contact_list.ts b/styles/src/style_tree/contact_list.ts index fb6b665a143c65a938111ee6366d775587f04b95..b3b89b7e428ac51be854d263e142dcb6c2191986 100644 --- a/styles/src/style_tree/contact_list.ts +++ b/styles/src/style_tree/contact_list.ts @@ -1,24 +1,30 @@ import { ColorScheme } from "../theme/color_scheme" -import { background, border, borderColor, foreground, text } from "./components" +import { + background, + border, + border_color, + foreground, + text, +} from "./components" import { interactive, toggleable } from "../element" -export default function contacts_panel(colorScheme: ColorScheme): any { +export default function contacts_panel(theme: ColorScheme): any { const nameMargin = 8 const sidePadding = 12 - const layer = colorScheme.middle + const layer = theme.middle const contactButton = { background: background(layer, "on"), color: foreground(layer, "on"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, + icon_width: 8, + button_width: 16, + corner_radius: 8, } const projectRow = { - guestAvatarSpacing: 4, + guest_avatar_spacing: 4, height: 24, - guestAvatar: { - cornerRadius: 8, + guest_avatar: { + corner_radius: 8, width: 14, }, name: { @@ -43,14 +49,14 @@ export default function contacts_panel(colorScheme: ColorScheme): any { return { background: background(layer), padding: { top: 12 }, - userQueryEditor: { + user_query_editor: { background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { + placeholder_text: text(layer, "mono", "on", "disabled", { size: "xs", }), - selection: colorScheme.players[0], + selection: theme.players[0], border: border(layer, "on"), padding: { bottom: 4, @@ -62,16 +68,16 @@ export default function contacts_panel(colorScheme: ColorScheme): any { left: 6, }, }, - userQueryEditorHeight: 33, - addContactButton: { + user_query_editor_height: 33, + add_contact_button: { margin: { left: 6, right: 12 }, color: foreground(layer, "on"), - buttonWidth: 28, - iconWidth: 16, + button_width: 28, + icon_width: 16, }, - rowHeight: 28, - sectionIconSize: 8, - headerRow: toggleable({ + row_height: 28, + section_icon_size: 8, + header_row: toggleable({ base: interactive({ base: { ...text(layer, "mono", { size: "sm" }), @@ -106,11 +112,11 @@ export default function contacts_panel(colorScheme: ColorScheme): any { }, }, }), - leaveCall: interactive({ + leave_call: interactive({ base: { background: background(layer), border: border(layer), - cornerRadius: 6, + corner_radius: 6, margin: { top: 1, }, @@ -130,7 +136,7 @@ export default function contacts_panel(colorScheme: ColorScheme): any { }, }, }), - contactRow: { + contact_row: { inactive: { default: { padding: { @@ -149,31 +155,30 @@ export default function contacts_panel(colorScheme: ColorScheme): any { }, }, }, - - contactAvatar: { - cornerRadius: 10, + contact_avatar: { + corner_radius: 10, width: 18, }, - contactStatusFree: { - cornerRadius: 4, + contact_status_free: { + corner_radius: 4, padding: 4, margin: { top: 12, left: 12 }, background: foreground(layer, "positive"), }, - contactStatusBusy: { - cornerRadius: 4, + contact_status_busy: { + corner_radius: 4, padding: 4, margin: { top: 12, left: 12 }, background: foreground(layer, "negative"), }, - contactUsername: { + contact_username: { ...text(layer, "mono", { size: "sm" }), margin: { left: nameMargin, }, }, - contactButtonSpacing: nameMargin, - contactButton: interactive({ + contact_button_spacing: nameMargin, + contact_button: interactive({ base: { ...contactButton }, state: { hovered: { @@ -181,35 +186,35 @@ export default function contacts_panel(colorScheme: ColorScheme): any { }, }, }), - disabledButton: { + disabled_button: { ...contactButton, background: background(layer, "on"), color: foreground(layer, "on"), }, - callingIndicator: { + calling_indicator: { ...text(layer, "mono", "variant", { size: "xs" }), }, - treeBranch: toggleable({ + tree_branch: toggleable({ base: interactive({ base: { - color: borderColor(layer), + color: border_color(layer), width: 1, }, state: { hovered: { - color: borderColor(layer), + color: border_color(layer), }, }, }), state: { active: { default: { - color: borderColor(layer), + color: border_color(layer), }, }, }, }), - projectRow: toggleable({ + project_row: toggleable({ base: interactive({ base: { ...projectRow, diff --git a/styles/src/style_tree/contact_notification.ts b/styles/src/style_tree/contact_notification.ts index 6ae2dc5d2037f1d1cea868ab6517000d22e8b52e..71467f6584493a44bea86e68cd5880dc7bcd692e 100644 --- a/styles/src/style_tree/contact_notification.ts +++ b/styles/src/style_tree/contact_notification.ts @@ -4,48 +4,47 @@ import { interactive } from "../element" const avatarSize = 12 const headerPadding = 8 -export default function contact_notification(colorScheme: ColorScheme): any { - const layer = colorScheme.lowest +export default function contact_notification(theme: ColorScheme): any { return { - headerAvatar: { + header_avatar: { height: avatarSize, width: avatarSize, - cornerRadius: 6, + corner_radius: 6, }, - headerMessage: { - ...text(layer, "sans", { size: "xs" }), + header_message: { + ...text(theme.lowest, "sans", { size: "xs" }), margin: { left: headerPadding, right: headerPadding }, }, - headerHeight: 18, - bodyMessage: { - ...text(layer, "sans", { size: "xs" }), + header_height: 18, + body_message: { + ...text(theme.lowest, "sans", { size: "xs" }), margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 }, }, button: interactive({ base: { - ...text(layer, "sans", "on", { size: "xs" }), - background: background(layer, "on"), + ...text(theme.lowest, "sans", "on", { size: "xs" }), + background: background(theme.lowest, "on"), padding: 4, - cornerRadius: 6, + corner_radius: 6, margin: { left: 6 }, }, state: { hovered: { - background: background(layer, "on", "hovered"), + background: background(theme.lowest, "on", "hovered"), }, }, }), - dismissButton: { + dismiss_button: { default: { - color: foreground(layer, "variant"), - iconWidth: 8, + color: foreground(theme.lowest, "variant"), + icon_width: 8, iconHeight: 8, - buttonWidth: 8, + button_width: 8, buttonHeight: 8, hover: { - color: foreground(layer, "hovered"), + color: foreground(theme.lowest, "hovered"), }, }, }, diff --git a/styles/src/style_tree/contacts_popover.ts b/styles/src/style_tree/contacts_popover.ts index 0c9d1a47eb4d2d48d6ffc51124b5eea0b2a1d5db..85c7e7d9f5d0ca6bbb88c4f94d4948fdeb21adeb 100644 --- a/styles/src/style_tree/contacts_popover.ts +++ b/styles/src/style_tree/contacts_popover.ts @@ -5,7 +5,7 @@ export default function contacts_popover(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { background: background(layer), - cornerRadius: 6, + corner_radius: 6, padding: { top: 6, bottom: 6 }, shadow: colorScheme.popoverShadow, border: border(layer), diff --git a/styles/src/style_tree/context_menu.ts b/styles/src/style_tree/context_menu.ts index 52795be79684202c0381ccc4daeb1d868289d31f..69ab7de4968ec8d96233069d934a7ed2e14d971d 100644 --- a/styles/src/style_tree/context_menu.ts +++ b/styles/src/style_tree/context_menu.ts @@ -1,12 +1,12 @@ import { ColorScheme } from "../theme/color_scheme" -import { background, border, borderColor, text } from "./components" +import { background, border, border_color, text } from "./components" import { interactive, toggleable } from "../element" export default function context_menu(colorScheme: ColorScheme): any { const layer = colorScheme.middle return { background: background(layer), - cornerRadius: 10, + corner_radius: 10, padding: 4, shadow: colorScheme.popoverShadow, border: border(layer), @@ -15,9 +15,9 @@ export default function context_menu(colorScheme: ColorScheme): any { base: interactive({ base: { iconSpacing: 8, - iconWidth: 14, + icon_width: 14, padding: { left: 6, right: 6, top: 2, bottom: 2 }, - cornerRadius: 6, + corner_radius: 6, label: text(layer, "sans", { size: "sm" }), keystroke: { ...text(layer, "sans", "variant", { @@ -60,7 +60,7 @@ export default function context_menu(colorScheme: ColorScheme): any { }), separator: { - background: borderColor(layer), + background: border_color(layer), margin: { top: 2, bottom: 2 }, }, } diff --git a/styles/src/style_tree/copilot.ts b/styles/src/style_tree/copilot.ts index cd2bbe0584fd145e3b24270a911e53fae8e7bc89..9f80f6f34c7caa6b9c7c6b0187311e23269c8b88 100644 --- a/styles/src/style_tree/copilot.ts +++ b/styles/src/style_tree/copilot.ts @@ -12,7 +12,7 @@ export default function copilot(colorScheme: ColorScheme): any { base: { background: background(layer), border: border(layer, "default"), - cornerRadius: 4, + corner_radius: 4, margin: { top: 4, bottom: 4, @@ -46,7 +46,7 @@ export default function copilot(colorScheme: ColorScheme): any { 12 ), container: { - cornerRadius: 6, + corner_radius: 6, padding: { left: 6 }, }, }, @@ -93,7 +93,7 @@ export default function copilot(colorScheme: ColorScheme): any { 8 ), container: { - cornerRadius: 2, + corner_radius: 2, padding: { top: 4, bottom: 4, @@ -246,7 +246,7 @@ export default function copilot(colorScheme: ColorScheme): any { }), border: border(layer, "warning"), background: background(layer, "warning"), - cornerRadius: 2, + corner_radius: 2, padding: { top: 4, left: 4, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 9bf46055808c75a0da66180066fce46094504db7..14df1b0bb3953cc5bbd559175bc0d8cc30ffe76b 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -1,6 +1,12 @@ import { withOpacity } from "../theme/color" import { ColorScheme, Layer, StyleSets } from "../theme/color_scheme" -import { background, border, borderColor, foreground, text } from "./components" +import { + background, + border, + border_color, + foreground, + text, +} from "./components" import hoverPopover from "./hover_popover" import { buildSyntax } from "../theme/syntax" @@ -12,7 +18,7 @@ export default function editor(colorScheme: ColorScheme): any { const layer = colorScheme.highest const autocompleteItem = { - cornerRadius: 6, + corner_radius: 6, padding: { bottom: 2, left: 6, @@ -111,7 +117,7 @@ export default function editor(colorScheme: ColorScheme): any { }), ellipses: { textColor: colorScheme.ramps.neutral(0.71).hex(), - cornerRadiusFactor: 0.15, + corner_radiusFactor: 0.15, background: { // Copied from hover_popover highlight default: { @@ -141,7 +147,7 @@ export default function editor(colorScheme: ColorScheme): any { : colorScheme.ramps.green(0.5).hex(), removedWidthEm: 0.275, widthEm: 0.15, - cornerRadius: 0.05, + corner_radius: 0.05, }, /** Highlights matching occurrences of what is under the cursor * as well as matched brackets @@ -174,7 +180,7 @@ export default function editor(colorScheme: ColorScheme): any { ], autocomplete: { background: background(colorScheme.middle), - cornerRadius: 8, + corner_radius: 8, padding: 4, margin: { left: -14, @@ -204,7 +210,7 @@ export default function editor(colorScheme: ColorScheme): any { }, diagnosticHeader: { background: background(colorScheme.middle), - iconWidthFactor: 1.5, + icon_widthFactor: 1.5, textScaleFactor: 0.857, border: border(colorScheme.middle, { bottom: true, @@ -257,9 +263,9 @@ export default function editor(colorScheme: ColorScheme): any { jumpIcon: interactive({ base: { color: foreground(layer, "on"), - iconWidth: 20, - buttonWidth: 20, - cornerRadius: 6, + icon_width: 20, + button_width: 20, + corner_radius: 6, padding: { top: 6, bottom: 6, @@ -284,7 +290,7 @@ export default function editor(colorScheme: ColorScheme): any { background: withOpacity(background(layer, "inverted"), 0.3), border: { width: 1, - color: borderColor(layer, "variant"), + color: border_color(layer, "variant"), top: false, right: true, left: true, @@ -306,7 +312,7 @@ export default function editor(colorScheme: ColorScheme): any { compositionMark: { underline: { thickness: 1.0, - color: borderColor(layer), + color: border_color(layer), }, }, syntax, diff --git a/styles/src/style_tree/feedback.ts b/styles/src/style_tree/feedback.ts index 88f3cebc37e7da2ebd5bf33799ee1a392edfd6cd..040c8994be10b05395c668adb09e63600122be22 100644 --- a/styles/src/style_tree/feedback.ts +++ b/styles/src/style_tree/feedback.ts @@ -10,7 +10,7 @@ export default function feedback(colorScheme: ColorScheme): any { base: { ...text(layer, "mono", "on"), background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, border: border(layer, "on"), margin: { right: 4, diff --git a/styles/src/style_tree/hover_popover.ts b/styles/src/style_tree/hover_popover.ts index 68eba194948b7217b9db0f7681c5a3dbf90722a9..42c4f72a263469369fd98ea0ae8f65270767a9ed 100644 --- a/styles/src/style_tree/hover_popover.ts +++ b/styles/src/style_tree/hover_popover.ts @@ -5,7 +5,7 @@ export default function hover_popover(colorScheme: ColorScheme): any { const layer = colorScheme.middle const baseContainer = { background: background(layer), - cornerRadius: 8, + corner_radius: 8, padding: { left: 8, right: 8, diff --git a/styles/src/style_tree/incoming_call_notification.ts b/styles/src/style_tree/incoming_call_notification.ts index 9a6d400017f734ba5e7bd2a3a35ccf000a466b53..2d76da7de82eef92a158fb5d2fa2870c085f547b 100644 --- a/styles/src/style_tree/incoming_call_notification.ts +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -16,7 +16,7 @@ export default function incoming_call_notification( callerAvatar: { height: avatarSize, width: avatarSize, - cornerRadius: avatarSize / 2, + corner_radius: avatarSize / 2, }, callerMetadata: { margin: { left: 10 }, @@ -33,7 +33,7 @@ export default function incoming_call_notification( ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), margin: { top: -3 }, }, - buttonWidth: 96, + button_width: 96, acceptButton: { background: background(layer, "accent"), border: border(layer, { left: true, bottom: true }), diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index 417c1faea4c429315a5c439065a77f0672a06eef..99bd716c16133be1e17b07dff16a1b9788c8da8f 100644 --- a/styles/src/style_tree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -9,7 +9,7 @@ export default function picker(colorScheme: ColorScheme): any { background: background(layer), border: border(layer), shadow: colorScheme.modalShadow, - cornerRadius: 12, + corner_radius: 12, padding: { bottom: 4, }, @@ -53,7 +53,7 @@ export default function picker(colorScheme: ColorScheme): any { left: 4, right: 4, }, - cornerRadius: 8, + corner_radius: 8, text: text(layer, "sans", "variant"), highlightText: text(layer, "sans", "accent", { weight: "bold", diff --git a/styles/src/style_tree/project_diagnostics.ts b/styles/src/style_tree/project_diagnostics.ts index f3f5a5a144653f3573057e19e8c462c553c5f3b7..10f556d12150332a79a506370096651bae217d1e 100644 --- a/styles/src/style_tree/project_diagnostics.ts +++ b/styles/src/style_tree/project_diagnostics.ts @@ -6,7 +6,7 @@ export default function project_diagnostics(colorScheme: ColorScheme): any { return { background: background(layer), tabIconSpacing: 4, - tabIconWidth: 13, + tab_icon_width: 13, tabSummarySpacing: 10, emptyMessage: text(layer, "sans", "variant", { size: "md" }), } diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index c38c689f4fe3e02801c07d931228bf7c689dbbb4..ac5c577e2114e4bad8aa2d3513c896aeec24fcc0 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -117,7 +117,7 @@ export default function project_panel(theme: ColorScheme): any { base: { background: background(layer), border: border(layer, "active"), - cornerRadius: 4, + corner_radius: 4, margin: { top: 16, left: 16, diff --git a/styles/src/style_tree/project_shared_notification.ts b/styles/src/style_tree/project_shared_notification.ts index f8b103e2e497859445ee82b914b88f80a56a6945..8437ab6c1bb628206bd9367691ea448f189a7873 100644 --- a/styles/src/style_tree/project_shared_notification.ts +++ b/styles/src/style_tree/project_shared_notification.ts @@ -17,7 +17,7 @@ export default function project_shared_notification( ownerAvatar: { height: avatarSize, width: avatarSize, - cornerRadius: avatarSize / 2, + corner_radius: avatarSize / 2, }, ownerMetadata: { margin: { left: 10 }, @@ -34,7 +34,7 @@ export default function project_shared_notification( ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), margin: { top: -3 }, }, - buttonWidth: 96, + button_width: 96, openButton: { background: background(layer, "accent"), border: border(layer, { left: true, bottom: true }), diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index c236284fa06ed1f7839ea7cefcaf5a842aac7468..37040613b303bee0a032756e7359f24a32ae1170 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -9,7 +9,7 @@ export default function search(colorScheme: ColorScheme): any { // Search input const editor = { background: background(layer), - cornerRadius: 8, + corner_radius: 8, minWidth: 200, maxWidth: 500, placeholderText: text(layer, "mono", "disabled"), @@ -41,7 +41,7 @@ export default function search(colorScheme: ColorScheme): any { base: { ...text(layer, "mono", "on"), background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, border: border(layer, "on"), margin: { right: 4, @@ -115,8 +115,8 @@ export default function search(colorScheme: ColorScheme): any { dismissButton: interactive({ base: { color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: 14, + icon_width: 12, + button_width: 14, padding: { left: 10, right: 10, diff --git a/styles/src/style_tree/shared_screen.ts b/styles/src/style_tree/shared_screen.ts index 718ea20642dd401f14d1f35c80c87921b3349b44..bc4ac0b5d705cca2d2f25597ba26681bbfc6feaf 100644 --- a/styles/src/style_tree/shared_screen.ts +++ b/styles/src/style_tree/shared_screen.ts @@ -1,10 +1,7 @@ import { ColorScheme } from "../theme/color_scheme" -import { StyleTree } from "../types" import { background } from "./components" -export default function sharedScreen( - colorScheme: ColorScheme -): StyleTree.SharedScreen { +export default function sharedScreen(colorScheme: ColorScheme) { const layer = colorScheme.highest return { background: background(layer), diff --git a/styles/src/style_tree/simple_message_notification.ts b/styles/src/style_tree/simple_message_notification.ts index 207924a99fbafddec7b4b3e78e03ea60d1e8a58d..621bf21232a15bb665bdab91b0a5b738fd251730 100644 --- a/styles/src/style_tree/simple_message_notification.ts +++ b/styles/src/style_tree/simple_message_notification.ts @@ -17,7 +17,7 @@ export default function simple_message_notification( base: { ...text(layer, "sans", { size: "xs" }), border: border(layer, "active"), - cornerRadius: 4, + corner_radius: 4, padding: { top: 3, bottom: 3, @@ -38,9 +38,9 @@ export default function simple_message_notification( dismissButton: interactive({ base: { color: foreground(layer), - iconWidth: 8, + icon_width: 8, iconHeight: 8, - buttonWidth: 8, + button_width: 8, buttonHeight: 8, }, state: { diff --git a/styles/src/style_tree/status_bar.ts b/styles/src/style_tree/status_bar.ts index e86afb1c0da2acd81d8a4f6635bcb3555e78b48e..fb1c572615152159c63b3739324ed97f1c585103 100644 --- a/styles/src/style_tree/status_bar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -5,12 +5,12 @@ export default function status_bar(colorScheme: ColorScheme): any { const layer = colorScheme.lowest const statusContainer = { - cornerRadius: 6, + corner_radius: 6, padding: { top: 3, bottom: 3, left: 6, right: 6 }, } const diagnosticStatusContainer = { - cornerRadius: 6, + corner_radius: 6, padding: { top: 1, bottom: 1, left: 6, right: 6 }, } @@ -42,7 +42,7 @@ export default function status_bar(colorScheme: ColorScheme): any { base: { ...diagnosticStatusContainer, iconSpacing: 4, - iconWidth: 14, + icon_width: 14, height: 18, message: text(layer, "sans"), iconColor: foreground(layer), @@ -64,7 +64,7 @@ export default function status_bar(colorScheme: ColorScheme): any { diagnosticSummary: interactive({ base: { height: 20, - iconWidth: 16, + icon_width: 16, iconSpacing: 2, summarySpacing: 6, text: text(layer, "sans", { size: "sm" }), @@ -72,7 +72,7 @@ export default function status_bar(colorScheme: ColorScheme): any { iconColorWarning: foreground(layer, "warning"), iconColorError: foreground(layer, "negative"), containerOk: { - cornerRadius: 6, + corner_radius: 6, padding: { top: 3, bottom: 3, left: 7, right: 7 }, }, containerWarning: { @@ -143,7 +143,7 @@ export default function status_bar(colorScheme: ColorScheme): any { }, }), badge: { - cornerRadius: 3, + corner_radius: 3, padding: 2, margin: { bottom: -1, right: -1 }, border: border(layer), diff --git a/styles/src/style_tree/tab_bar.ts b/styles/src/style_tree/tab_bar.ts index fe43934f79fa4a491009355f95249f5d6ad71ec7..d8323809edd2118876484cfa8f1f77f5b3674335 100644 --- a/styles/src/style_tree/tab_bar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -25,10 +25,10 @@ export default function tab_bar(colorScheme: ColorScheme): any { spacing: 8, // Tab type icons (e.g. Project Search) - typeIconWidth: 14, + type_icon_width: 14, // Close icons - closeIconWidth: 8, + close_icon_width: 8, iconClose: foreground(layer, "variant"), iconCloseActive: foreground(layer, "hovered"), @@ -92,8 +92,8 @@ export default function tab_bar(colorScheme: ColorScheme): any { base: interactive({ base: { color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: activePaneActiveTab.height, + icon_width: 12, + button_width: activePaneActiveTab.height, }, state: { hovered: { diff --git a/styles/src/style_tree/terminal.ts b/styles/src/style_tree/terminal.ts index 4e3dc18627c3262b78acc402e67352100e7aa2dc..e902aa42647a56b1de3642a0c89673863a62754f 100644 --- a/styles/src/style_tree/terminal.ts +++ b/styles/src/style_tree/terminal.ts @@ -1,7 +1,6 @@ import { ColorScheme } from "../theme/color_scheme" -import { StyleTree } from "../types" -export default function terminal(theme: ColorScheme): StyleTree.TerminalStyle { +export default function terminal(theme: ColorScheme) { /** * Colors are controlled per-cell in the terminal grid. * Cells can be set to any of these more 'theme-capable' colors diff --git a/styles/src/style_tree/titlebar.ts b/styles/src/style_tree/titlebar.ts index a5bcdc9492d38a20f0e2863884f0ac9b2c447f44..dc1d098a3c3369aeb061764f2c2f38e7f21038a1 100644 --- a/styles/src/style_tree/titlebar.ts +++ b/styles/src/style_tree/titlebar.ts @@ -78,7 +78,7 @@ function user_menu(theme: ColorScheme) { const button = toggleable({ base: interactive({ base: { - cornerRadius: 6, + corner_radius: 6, height: button_height, width: online ? 37 : 24, padding: { @@ -180,13 +180,13 @@ export function titlebar(theme: ColorScheme): any { leaderAvatar: { width: avatarWidth, outerWidth: avatarOuterWidth, - cornerRadius: avatarWidth / 2, + corner_radius: avatarWidth / 2, outerCornerRadius: avatarOuterWidth / 2, }, followerAvatar: { width: followerAvatarWidth, outerWidth: followerAvatarOuterWidth, - cornerRadius: followerAvatarWidth / 2, + corner_radius: followerAvatarWidth / 2, outerCornerRadius: followerAvatarOuterWidth / 2, }, inactiveAvatarGrayscale: true, @@ -202,7 +202,7 @@ export function titlebar(theme: ColorScheme): any { top: 2, bottom: 2, }, - cornerRadius: 6, + corner_radius: 6, }, avatarRibbon: { height: 3, @@ -234,7 +234,7 @@ export function titlebar(theme: ColorScheme): any { left: 8, right: 8, }, - cornerRadius: 6, + corner_radius: 6, }, leave_call_button: icon_button(theme, { @@ -254,7 +254,7 @@ export function titlebar(theme: ColorScheme): any { // Jewel that notifies you that there are new contact requests toggleContactsBadge: { - cornerRadius: 3, + corner_radius: 3, padding: 2, margin: { top: 3, left: 3 }, border: border(theme.lowest), diff --git a/styles/src/style_tree/toolbar_dropdown_menu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts index 992631e0363eb668695d3ebaa2633e0c5a2bd077..5d0bf89d93eb27f82b5f94e7b9450129ea15eee5 100644 --- a/styles/src/style_tree/toolbar_dropdown_menu.ts +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -18,7 +18,7 @@ export default function dropdown_menu(colorScheme: ColorScheme): any { }), secondaryTextSpacing: 10, padding: { left: 8, right: 8, top: 2, bottom: 2 }, - cornerRadius: 6, + corner_radius: 6, background: background(layer, "on"), }, state: { diff --git a/styles/src/style_tree/tooltip.ts b/styles/src/style_tree/tooltip.ts index 54daae529e55782cc11f659a943461ca92ab3b9c..a872477f4987680207b75cbe2bed6f6329e300af 100644 --- a/styles/src/style_tree/tooltip.ts +++ b/styles/src/style_tree/tooltip.ts @@ -9,11 +9,11 @@ export default function tooltip(colorScheme: ColorScheme): any { padding: { top: 4, bottom: 4, left: 8, right: 8 }, margin: { top: 6, left: 6 }, shadow: colorScheme.popoverShadow, - cornerRadius: 6, + corner_radius: 6, text: text(layer, "sans", { size: "xs" }), keystroke: { background: background(layer, "on"), - cornerRadius: 4, + corner_radius: 4, margin: { left: 6 }, padding: { left: 4, right: 4 }, ...text(layer, "mono", "on", { size: "xs", weight: "bold" }), diff --git a/styles/src/style_tree/update_notification.ts b/styles/src/style_tree/update_notification.ts index c6e6130f540c1b7fc448dce99ff7048bdd2d71f5..e3cf833ce85e0e83ac655b6285485af1c882be13 100644 --- a/styles/src/style_tree/update_notification.ts +++ b/styles/src/style_tree/update_notification.ts @@ -25,9 +25,9 @@ export default function update_notification(colorScheme: ColorScheme): any { dismissButton: interactive({ base: { color: foreground(layer), - iconWidth: 8, + icon_width: 8, iconHeight: 8, - buttonWidth: 8, + button_width: 8, buttonHeight: 8, }, state: { diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts index 93f18267401da2a5abc9b39defbc505824b634ab..5d0bc90a00d1e4be6d7c7cfb4f2553cf2b8739cd 100644 --- a/styles/src/style_tree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -14,7 +14,7 @@ export default function welcome(colorScheme: ColorScheme): any { const layer = colorScheme.highest const checkboxBase = { - cornerRadius: 4, + corner_radius: 4, padding: { left: 3, right: 3, @@ -57,7 +57,7 @@ export default function welcome(colorScheme: ColorScheme): any { checkboxGroup: { border: border(layer, "variant"), background: withOpacity(background(layer, "hovered"), 0.25), - cornerRadius: 4, + corner_radius: 4, padding: { left: 12, top: 2, @@ -68,7 +68,7 @@ export default function welcome(colorScheme: ColorScheme): any { base: { background: background(layer), border: border(layer, "active"), - cornerRadius: 4, + corner_radius: 4, margin: { top: 4, bottom: 4, diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index 48217e89c74234f324c9788142e8721e57004b46..2ba0281b17ef6ae9976a88d77842245512b75252 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -3,7 +3,7 @@ import { withOpacity } from "../theme/color" import { background, border, - borderColor, + border_color, foreground, svg, text, @@ -46,7 +46,7 @@ export default function workspace(colorScheme: ColorScheme): any { margin: { top: 96, }, - cornerRadius: 4, + corner_radius: 4, }, keyboardHint: interactive({ base: { @@ -57,7 +57,7 @@ export default function workspace(colorScheme: ColorScheme): any { right: 8, bottom: 3, }, - cornerRadius: 8, + corner_radius: 8, }, state: { hovered: { @@ -69,7 +69,7 @@ export default function workspace(colorScheme: ColorScheme): any { keyboardHintWidth: 320, }, joiningProjectAvatar: { - cornerRadius: 40, + corner_radius: 40, width: 80, }, joiningProjectMessage: { @@ -79,7 +79,7 @@ export default function workspace(colorScheme: ColorScheme): any { externalLocationMessage: { background: background(colorScheme.middle, "accent"), border: border(colorScheme.middle, "accent"), - cornerRadius: 6, + corner_radius: 6, padding: 12, margin: { bottom: 8, right: 8 }, ...text(colorScheme.middle, "sans", "accent", { size: "xs" }), @@ -121,7 +121,7 @@ export default function workspace(colorScheme: ColorScheme): any { }, }, paneDivider: { - color: borderColor(layer), + color: border_color(layer), width: 1, }, statusBar: statusBar(colorScheme), @@ -134,9 +134,9 @@ export default function workspace(colorScheme: ColorScheme): any { navButton: interactive({ base: { color: foreground(colorScheme.highest, "on"), - iconWidth: 12, - buttonWidth: 24, - cornerRadius: 6, + icon_width: 12, + button_width: 24, + corner_radius: 6, }, state: { hovered: { @@ -162,7 +162,7 @@ export default function workspace(colorScheme: ColorScheme): any { breadcrumbs: interactive({ base: { ...text(colorScheme.highest, "sans", "variant"), - cornerRadius: 6, + corner_radius: 6, padding: { left: 6, right: 6, @@ -186,7 +186,7 @@ export default function workspace(colorScheme: ColorScheme): any { notification: { margin: { top: 10 }, background: background(colorScheme.middle), - cornerRadius: 6, + corner_radius: 6, padding: 12, border: border(colorScheme.middle), shadow: colorScheme.popoverShadow, diff --git a/styles/src/theme/color_scheme.ts b/styles/src/theme/color_scheme.ts index 61b459911b96aec679ae58b57bfc477470344483..5d62bcffdbfe7eebd27b37995153df508437a2e4 100644 --- a/styles/src/theme/color_scheme.ts +++ b/styles/src/theme/color_scheme.ts @@ -252,11 +252,7 @@ function buildStyleSet( } } -function buildStyleDefinition( - bgBase: number, - fgBase: number, - step = 0.08 -) { +function buildStyleDefinition(bgBase: number, fgBase: number, step = 0.08) { return { background: { default: bgBase, diff --git a/styles/src/types/element.ts b/styles/src/types/element.ts deleted file mode 100644 index d6da5f9b714d05db89181d41702c6db1d9273569..0000000000000000000000000000000000000000 --- a/styles/src/types/element.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Clean } from "./util" -import * as zed from "./zed" - -export type Text = Clean diff --git a/styles/src/types/index.ts b/styles/src/types/index.ts deleted file mode 100644 index 3a017feb280589b65ea35966bb8bc1368b29c72e..0000000000000000000000000000000000000000 --- a/styles/src/types/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as StyleTree from "./styleTree" -import * as Property from "./property" -import * as Element from "./element" - -export { StyleTree, Property, Element } diff --git a/styles/src/types/property.ts b/styles/src/types/property.ts deleted file mode 100644 index 6205b725ef02782478f583c36d700788643488eb..0000000000000000000000000000000000000000 --- a/styles/src/types/property.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Clean } from "./util" -import * as zed from "./zed" - -export type Color = zed.Color -export type CursorStyle = zed.CursorStyle -export type FontStyle = zed.Style -export type Border = Clean -export type Margin = Clean -export type Padding = Clean diff --git a/styles/src/types/style_tree.ts b/styles/src/types/style_tree.ts deleted file mode 100644 index 08ae6833497367cbb6a90943072cc78f43a37ff5..0000000000000000000000000000000000000000 --- a/styles/src/types/style_tree.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Clean } from "./util" -import * as zed from "./zed" - -export type AssistantStyle = Readonly> -export type CommandPalette = Readonly> -export type ContactFinder = Readonly> -export type ContactList = Readonly> -export type ContactNotification = Readonly> -export type ContactsPopover = Readonly> -export type ContextMenu = Readonly> -export type Copilot = Readonly> -export type Editor = Readonly> -export type FeedbackStyle = Readonly> -export type IncomingCallNotification = Readonly< - Clean -> -export type ThemeMeta = Readonly> -export type Picker = Readonly> -export type ProjectDiagnostics = Readonly> -export type ProjectPanel = Readonly> -export type ProjectSharedNotification = Readonly< - Clean -> -export type Search = Readonly> -export type SharedScreen = Readonly> -export type MessageNotification = Readonly> -export type TerminalStyle = Readonly> -export type UserMenu = Readonly> -export type DropdownMenu = Readonly> -export type TooltipStyle = Readonly> -export type UpdateNotification = Readonly> -export type WelcomeStyle = Readonly> -export type Workspace = Readonly> diff --git a/styles/src/types/util.ts b/styles/src/types/util.ts deleted file mode 100644 index 99a742124a378855ed73125a5d573dee756df455..0000000000000000000000000000000000000000 --- a/styles/src/types/util.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type Prettify = { - [K in keyof T]: T[K] -} & unknown - -/** - * Clean removes the [k: string]: unknown property from an object, - * and Prettifies it, providing better hover information for the type - */ -export type Clean = { - [K in keyof T as string extends K ? never : K]: T[K] -} - -export type DeepClean = { - [K in keyof T as string extends K ? never : K]: T[K] extends object - ? DeepClean - : T[K] -} diff --git a/styles/src/utils/snake_case.ts b/styles/src/utils/snake_case.ts index 38c8a90a9e864177dcea255fa5b87a87da5a9607..cdd9684752826c5606b0e0273b5286bca57c85e0 100644 --- a/styles/src/utils/snake_case.ts +++ b/styles/src/utils/snake_case.ts @@ -5,8 +5,8 @@ import { snakeCase } from "case-anything" // Typescript magic to convert any string from camelCase to snake_case at compile time type SnakeCase = S extends string ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S + ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + : S : S type SnakeCased = { diff --git a/styles/tsconfig.json b/styles/tsconfig.json index cf68509748e64500cdc75f2e64ebecee06265814..925935ebb5f2f1335f838f89465a6c8ad65ce312 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -24,33 +24,15 @@ "useUnknownInCatchVariables": false, "baseUrl": ".", "paths": { - "@/*": [ - "./*" - ], - "@element/*": [ - "./src/element/*" - ], - "@component/*": [ - "./src/component/*" - ], - "@styleTree/*": [ - "./src/styleTree/*" - ], - "@theme/*": [ - "./src/theme/*" - ], - "@types/*": [ - "./src/util/*" - ], - "@themes/*": [ - "./src/themes/*" - ], - "@util/*": [ - "./src/util/*" - ] + "@/*": ["./*"], + "@element/*": ["./src/element/*"], + "@component/*": ["./src/component/*"], + "@styleTree/*": ["./src/styleTree/*"], + "@theme/*": ["./src/theme/*"], + "@types/*": ["./src/util/*"], + "@themes/*": ["./src/themes/*"], + "@util/*": ["./src/util/*"] } }, - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] } From ba17fae8d9d62f11b42d13073bdd02842c10dbb7 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 01:48:40 -0400 Subject: [PATCH 031/169] WIP snake_case 2/? --- styles/.eslintrc.js | 11 ++ styles/mod.py | 42 ----- styles/package-lock.json | 10 ++ styles/package.json | 1 + styles/src/build_licenses.ts | 30 ++-- styles/src/build_themes.ts | 38 ++--- styles/src/build_tokens.ts | 70 ++++---- styles/src/build_types.ts | 36 ++--- styles/src/common.ts | 24 +-- styles/src/style_tree/components.ts | 4 +- styles/src/style_tree/contacts_popover.ts | 9 +- styles/src/style_tree/context_menu.ts | 33 ++-- styles/src/style_tree/copilot.ts | 83 +++++----- styles/src/style_tree/editor.ts | 150 +++++++++--------- .../style_tree/incoming_call_notification.ts | 4 +- styles/src/style_tree/project_panel.ts | 2 +- .../style_tree/project_shared_notification.ts | 4 +- styles/src/style_tree/status_bar.ts | 4 +- styles/src/theme/syntax.ts | 94 +++++------ styles/src/themes/gruvbox/gruvbox-common.ts | 4 +- styles/src/themes/one/one-dark.ts | 8 +- styles/src/themes/one/one-light.ts | 8 +- styles/src/themes/rose-pine/common.ts | 4 +- 23 files changed, 317 insertions(+), 356 deletions(-) delete mode 100644 styles/mod.py diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js index c131089e141419914f41bb5c1f623d554ddf95c1..0b4af9dcbc29895d3e83589972106da88d401c8a 100644 --- a/styles/.eslintrc.js +++ b/styles/.eslintrc.js @@ -29,6 +29,17 @@ module.exports = { rules: { "linebreak-style": ["error", "unix"], semi: ["error", "never"], + "@typescript-eslint/naming-convention": [ + "warn", + { + selector: ["property", "variableLike", "memberLike", "method"], + format: ["snake_case"], + }, + { + selector: ["typeLike"], + format: ["PascalCase"], + }, + ], "import/no-restricted-paths": [ "error", { diff --git a/styles/mod.py b/styles/mod.py deleted file mode 100644 index 77238e5b45428beab2100f6ea2f7be707ae52acd..0000000000000000000000000000000000000000 --- a/styles/mod.py +++ /dev/null @@ -1,42 +0,0 @@ -import os, sys, re - - -def camel_to_snake(inputstring): - REG = r'(?=10.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", diff --git a/styles/package.json b/styles/package.json index 06621e8cc1a6ec4e5f2fc854237f8cb202333b78..1b90b81048faf590625091b92089d7b40c4adc9f 100644 --- a/styles/package.json +++ b/styles/package.json @@ -41,6 +41,7 @@ "eslint": "^8.43.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", + "eslint-plugin-snakecasejs": "^2.2.0", "typescript": "^5.1.5" } } diff --git a/styles/src/build_licenses.ts b/styles/src/build_licenses.ts index 93a2bd302aa1d88cbd2050b681c8fe110a3bef1f..88cefe15c7beaa913b5721a0c1064ff7d5ceb8e8 100644 --- a/styles/src/build_licenses.ts +++ b/styles/src/build_licenses.ts @@ -6,7 +6,7 @@ import { ThemeConfig } from "./common" const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses.toml` // Use the cargo-about configuration file as the source of truth for supported licenses. -function parseAcceptedToml(file: string): string[] { +function parse_accepted_toml(file: string): string[] { const buffer = fs.readFileSync(file).toString() const obj = toml.parse(buffer) @@ -18,7 +18,7 @@ function parseAcceptedToml(file: string): string[] { return obj.accepted } -function checkLicenses(themes: ThemeConfig[]) { +function check_licenses(themes: ThemeConfig[]) { for (const theme of themes) { if (!theme.licenseFile) { throw Error(`Theme ${theme.name} should have a LICENSE file`) @@ -26,25 +26,25 @@ function checkLicenses(themes: ThemeConfig[]) { } } -function generateLicenseFile(themes: ThemeConfig[]) { - checkLicenses(themes) +function generate_license_file(themes: ThemeConfig[]) { + check_licenses(themes) for (const theme of themes) { - const licenseText = fs.readFileSync(theme.licenseFile).toString() - writeLicense(theme.name, licenseText, theme.licenseUrl) + const license_text = fs.readFileSync(theme.licenseFile).toString() + write_license(theme.name, license_text, theme.licenseUrl) } } -function writeLicense( - themeName: string, - licenseText: string, - licenseUrl?: string +function write_license( + theme_name: string, + license_text: string, + license_url?: string ) { process.stdout.write( - licenseUrl - ? `## [${themeName}](${licenseUrl})\n\n${licenseText}\n********************************************************************************\n\n` - : `## ${themeName}\n\n${licenseText}\n********************************************************************************\n\n` + license_url + ? `## [${theme_name}](${license_url})\n\n${license_text}\n********************************************************************************\n\n` + : `## ${theme_name}\n\n${license_text}\n********************************************************************************\n\n` ) } -const acceptedLicenses = parseAcceptedToml(ACCEPTED_LICENSES_FILE) -generateLicenseFile(themes) +const accepted_licenses = parse_accepted_toml(ACCEPTED_LICENSES_FILE) +generate_license_file(themes) diff --git a/styles/src/build_themes.ts b/styles/src/build_themes.ts index 3ce1688152b7e939725ab5f9278bfe91117b1078..132b91e5826043b2d221abb95376ad0ccb8c0230 100644 --- a/styles/src/build_themes.ts +++ b/styles/src/build_themes.ts @@ -6,38 +6,38 @@ import { ColorScheme, createColorScheme } from "./theme/color_scheme" import snakeCase from "./utils/snake_case" import { themes } from "./themes" -const assetsDirectory = `${__dirname}/../../assets` -const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) +const assets_directory = `${__dirname}/../../assets` +const temp_directory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) // Clear existing themes -function clearThemes(themeDirectory: string) { - if (!fs.existsSync(themeDirectory)) { - fs.mkdirSync(themeDirectory, { recursive: true }) +function clear_themes(theme_directory: string) { + if (!fs.existsSync(theme_directory)) { + fs.mkdirSync(theme_directory, { recursive: true }) } else { - for (const file of fs.readdirSync(themeDirectory)) { + for (const file of fs.readdirSync(theme_directory)) { if (file.endsWith(".json")) { - fs.unlinkSync(path.join(themeDirectory, file)) + fs.unlinkSync(path.join(theme_directory, file)) } } } } -function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { - clearThemes(outputDirectory) - for (const colorScheme of colorSchemes) { - const styleTree = snakeCase(app(colorScheme)) - const styleTreeJSON = JSON.stringify(styleTree, null, 2) - const tempPath = path.join(tempDirectory, `${colorScheme.name}.json`) - const outPath = path.join(outputDirectory, `${colorScheme.name}.json`) - fs.writeFileSync(tempPath, styleTreeJSON) - fs.renameSync(tempPath, outPath) - console.log(`- ${outPath} created`) +function write_themes(color_schemes: ColorScheme[], output_directory: string) { + clear_themes(output_directory) + for (const color_scheme of color_schemes) { + const style_tree = snakeCase(app(color_scheme)) + const style_tree_json = JSON.stringify(style_tree, null, 2) + const temp_path = path.join(temp_directory, `${color_scheme.name}.json`) + const out_path = path.join(output_directory, `${color_scheme.name}.json`) + fs.writeFileSync(temp_path, style_tree_json) + fs.renameSync(temp_path, out_path) + console.log(`- ${out_path} created`) } } -const colorSchemes: ColorScheme[] = themes.map((theme) => +const color_schemes: ColorScheme[] = themes.map((theme) => createColorScheme(theme) ) // Write new themes to theme directory -writeThemes(colorSchemes, `${assetsDirectory}/themes`) +write_themes(color_schemes, `${assets_directory}/themes`) diff --git a/styles/src/build_tokens.ts b/styles/src/build_tokens.ts index e1878efed7297f96184f81b6075b4e02eb5f78a3..4e420d679a0cd317ad4224f1af1fd363a05f900b 100644 --- a/styles/src/build_tokens.ts +++ b/styles/src/build_tokens.ts @@ -3,19 +3,19 @@ import * as path from "path" import { ColorScheme, createColorScheme } from "./common" import { themes } from "./themes" import { slugify } from "./utils/slugify" -import { colorSchemeTokens } from "./theme/tokens/color_scheme" +import { colorSchemeTokens as color_scheme_tokens } from "./theme/tokens/color_scheme" const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens") const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json") const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json") -function clearTokens(tokensDirectory: string) { - if (!fs.existsSync(tokensDirectory)) { - fs.mkdirSync(tokensDirectory, { recursive: true }) +function clear_tokens(tokens_directory: string) { + if (!fs.existsSync(tokens_directory)) { + fs.mkdirSync(tokens_directory, { recursive: true }) } else { - for (const file of fs.readdirSync(tokensDirectory)) { + for (const file of fs.readdirSync(tokens_directory)) { if (file.endsWith(".json")) { - fs.unlinkSync(path.join(tokensDirectory, file)) + fs.unlinkSync(path.join(tokens_directory, file)) } } } @@ -24,64 +24,64 @@ function clearTokens(tokensDirectory: string) { type TokenSet = { id: string name: string - selectedTokenSets: { [key: string]: "enabled" } + selected_token_sets: { [key: string]: "enabled" } } -function buildTokenSetOrder(colorSchemes: ColorScheme[]): { - tokenSetOrder: string[] +function build_token_set_order(theme: ColorScheme[]): { + token_set_order: string[] } { - const tokenSetOrder: string[] = colorSchemes.map((scheme) => + const token_set_order: string[] = theme.map((scheme) => scheme.name.toLowerCase().replace(/\s+/g, "_") ) - return { tokenSetOrder } + return { token_set_order } } -function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] { - const themesIndex: TokenSet[] = colorSchemes.map((scheme, index) => { +function build_themes_index(theme: ColorScheme[]): TokenSet[] { + const themes_index: TokenSet[] = theme.map((scheme, index) => { const id = `${scheme.is_light ? "light" : "dark"}_${scheme.name .toLowerCase() .replace(/\s+/g, "_")}_${index}` - const selectedTokenSets: { [key: string]: "enabled" } = {} - const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_") - selectedTokenSets[tokenSet] = "enabled" + const selected_token_sets: { [key: string]: "enabled" } = {} + const token_set = scheme.name.toLowerCase().replace(/\s+/g, "_") + selected_token_sets[token_set] = "enabled" return { id, name: `${scheme.name} - ${scheme.is_light ? "Light" : "Dark"}`, - selectedTokenSets, + selected_token_sets, } }) - return themesIndex + return themes_index } -function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) { - clearTokens(tokensDirectory) +function write_tokens(themes: ColorScheme[], tokens_directory: string) { + clear_tokens(tokens_directory) - for (const colorScheme of colorSchemes) { - const fileName = slugify(colorScheme.name) + ".json" - const tokens = colorSchemeTokens(colorScheme) - const tokensJSON = JSON.stringify(tokens, null, 2) - const outPath = path.join(tokensDirectory, fileName) - fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 }) - console.log(`- ${outPath} created`) + for (const theme of themes) { + const file_name = slugify(theme.name) + ".json" + const tokens = color_scheme_tokens(theme) + const tokens_json = JSON.stringify(tokens, null, 2) + const out_path = path.join(tokens_directory, file_name) + fs.writeFileSync(out_path, tokens_json, { mode: 0o644 }) + console.log(`- ${out_path} created`) } - const themeIndexData = buildThemesIndex(colorSchemes) + const theme_index_data = build_themes_index(themes) - const themesJSON = JSON.stringify(themeIndexData, null, 2) - fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 }) + const themes_json = JSON.stringify(theme_index_data, null, 2) + fs.writeFileSync(TOKENS_FILE, themes_json, { mode: 0o644 }) console.log(`- ${TOKENS_FILE} created`) - const tokenSetOrderData = buildTokenSetOrder(colorSchemes) + const token_set_order_data = build_token_set_order(themes) - const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2) - fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 }) + const metadata_json = JSON.stringify(token_set_order_data, null, 2) + fs.writeFileSync(METADATA_FILE, metadata_json, { mode: 0o644 }) console.log(`- ${METADATA_FILE} created`) } -const colorSchemes: ColorScheme[] = themes.map((theme) => +const all_themes: ColorScheme[] = themes.map((theme) => createColorScheme(theme) ) -writeTokens(colorSchemes, TOKENS_DIRECTORY) +write_tokens(all_themes, TOKENS_DIRECTORY) diff --git a/styles/src/build_types.ts b/styles/src/build_types.ts index 5957ae5076a08fd62c84822edb946a0ab9a56f0a..5d7aa6e0ad259e62b60494f55436c70388017e14 100644 --- a/styles/src/build_types.ts +++ b/styles/src/build_types.ts @@ -9,34 +9,34 @@ const BANNER = `/* const dirname = __dirname async function main() { - const schemasPath = path.join(dirname, "../../", "crates/theme/schemas") - const schemaFiles = (await fs.readdir(schemasPath)).filter((x) => + const schemas_path = path.join(dirname, "../../", "crates/theme/schemas") + const schema_files = (await fs.readdir(schemas_path)).filter((x) => x.endsWith(".json") ) - const compiledTypes = new Set() + const compiled_types = new Set() - for (const filename of schemaFiles) { - const filePath = path.join(schemasPath, filename) - const fileContents = await fs.readFile(filePath) - const schema = JSON.parse(fileContents.toString()) + for (const filename of schema_files) { + const file_path = path.join(schemas_path, filename) + const file_contents = await fs.readFile(file_path) + const schema = JSON.parse(file_contents.toString()) const compiled = await compile(schema, schema.title, { bannerComment: "", }) - const eachType = compiled.split("export") - for (const type of eachType) { + const each_type = compiled.split("export") + for (const type of each_type) { if (!type) { continue } - compiledTypes.add("export " + type.trim()) + compiled_types.add("export " + type.trim()) } } - const output = BANNER + Array.from(compiledTypes).join("\n\n") - const outputPath = path.join(dirname, "../../styles/src/types/zed.ts") + const output = BANNER + Array.from(compiled_types).join("\n\n") + const output_path = path.join(dirname, "../../styles/src/types/zed.ts") try { - const existing = await fs.readFile(outputPath) + const existing = await fs.readFile(output_path) if (existing.toString() == output) { // Skip writing if it hasn't changed console.log("Schemas are up to date") @@ -48,12 +48,12 @@ async function main() { } } - const typesDic = path.dirname(outputPath) - if (!fsSync.existsSync(typesDic)) { - await fs.mkdir(typesDic) + const types_dic = path.dirname(output_path) + if (!fsSync.existsSync(types_dic)) { + await fs.mkdir(types_dic) } - await fs.writeFile(outputPath, output) - console.log(`Wrote Typescript types to ${outputPath}`) + await fs.writeFile(output_path, output) + console.log(`Wrote Typescript types to ${output_path}`) } main().catch((e) => { diff --git a/styles/src/common.ts b/styles/src/common.ts index ee47bcc6bdc01a0f64557374bcc66febd9a97873..6c90de40942c6975d218b2843b62030086257008 100644 --- a/styles/src/common.ts +++ b/styles/src/common.ts @@ -2,42 +2,26 @@ import chroma from "chroma-js" export * from "./theme" export { chroma } -export const fontFamilies = { +export const font_families = { sans: "Zed Sans", mono: "Zed Mono", } -export const fontSizes = { - "3xs": 8, +export const font_sizes = { "2xs": 10, xs: 12, sm: 14, md: 16, - lg: 18, - xl: 20, + lg: 18 } export type FontWeight = - | "thin" - | "extra_light" - | "light" | "normal" - | "medium" - | "semibold" | "bold" - | "extra_bold" - | "black" -export const fontWeights: { [key: string]: FontWeight } = { - thin: "thin", - extra_light: "extra_light", - light: "light", +export const font_weights: { [key: string]: FontWeight } = { normal: "normal", - medium: "medium", - semibold: "semibold", bold: "bold", - extra_bold: "extra_bold", - black: "black", } export const sizes = { diff --git a/styles/src/style_tree/components.ts b/styles/src/style_tree/components.ts index c0b8e9462fb71c69c77f14a16139564efb79dd94..6e20486631ab4dabd7b62a765f62ba7292a0b47e 100644 --- a/styles/src/style_tree/components.ts +++ b/styles/src/style_tree/components.ts @@ -1,6 +1,6 @@ import { - fontFamilies as font_families, - fontSizes as font_sizes, + font_families, + font_sizes, FontWeight, } from "../common" import { Layer, Styles, StyleSets, Style } from "../theme/color_scheme" diff --git a/styles/src/style_tree/contacts_popover.ts b/styles/src/style_tree/contacts_popover.ts index 85c7e7d9f5d0ca6bbb88c4f94d4948fdeb21adeb..2018da6b845d29dfc147c7a346d52161c8d08065 100644 --- a/styles/src/style_tree/contacts_popover.ts +++ b/styles/src/style_tree/contacts_popover.ts @@ -1,14 +1,13 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border } from "./components" -export default function contacts_popover(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function contacts_popover(theme: ColorScheme): any { return { - background: background(layer), + background: background(theme.middle), corner_radius: 6, padding: { top: 6, bottom: 6 }, - shadow: colorScheme.popoverShadow, - border: border(layer), + shadow: theme.popoverShadow, + border: border(theme.middle), width: 300, height: 400, } diff --git a/styles/src/style_tree/context_menu.ts b/styles/src/style_tree/context_menu.ts index 69ab7de4968ec8d96233069d934a7ed2e14d971d..df765e0a4a9daabd2f8074815da7ad71a3fb2f35 100644 --- a/styles/src/style_tree/context_menu.ts +++ b/styles/src/style_tree/context_menu.ts @@ -2,25 +2,24 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, border_color, text } from "./components" import { interactive, toggleable } from "../element" -export default function context_menu(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function context_menu(theme: ColorScheme): any { return { - background: background(layer), + background: background(theme.middle), corner_radius: 10, padding: 4, - shadow: colorScheme.popoverShadow, - border: border(layer), - keystrokeMargin: 30, + shadow: theme.popoverShadow, + border: border(theme.middle), + keystroke_margin: 30, item: toggleable({ base: interactive({ base: { - iconSpacing: 8, + icon_spacing: 8, icon_width: 14, padding: { left: 6, right: 6, top: 2, bottom: 2 }, corner_radius: 6, - label: text(layer, "sans", { size: "sm" }), + label: text(theme.middle, "sans", { size: "sm" }), keystroke: { - ...text(layer, "sans", "variant", { + ...text(theme.middle, "sans", "variant", { size: "sm", weight: "bold", }), @@ -29,10 +28,10 @@ export default function context_menu(colorScheme: ColorScheme): any { }, state: { hovered: { - background: background(layer, "hovered"), - label: text(layer, "sans", "hovered", { size: "sm" }), + background: background(theme.middle, "hovered"), + label: text(theme.middle, "sans", "hovered", { size: "sm" }), keystroke: { - ...text(layer, "sans", "hovered", { + ...text(theme.middle, "sans", "hovered", { size: "sm", weight: "bold", }), @@ -40,27 +39,27 @@ export default function context_menu(colorScheme: ColorScheme): any { }, }, clicked: { - background: background(layer, "pressed"), + background: background(theme.middle, "pressed"), }, }, }), state: { active: { default: { - background: background(layer, "active"), + background: background(theme.middle, "active"), }, hovered: { - background: background(layer, "hovered"), + background: background(theme.middle, "hovered"), }, clicked: { - background: background(layer, "pressed"), + background: background(theme.middle, "pressed"), }, }, }, }), separator: { - background: border_color(layer), + background: border_color(theme.middle), margin: { top: 2, bottom: 2 }, }, } diff --git a/styles/src/style_tree/copilot.ts b/styles/src/style_tree/copilot.ts index 9f80f6f34c7caa6b9c7c6b0187311e23269c8b88..7b0fc5e4ea69ee26f4c750e8a0884d5e418b6799 100644 --- a/styles/src/style_tree/copilot.ts +++ b/styles/src/style_tree/copilot.ts @@ -1,17 +1,16 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, svg, text } from "./components" import { interactive } from "../element" -export default function copilot(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function copilot(theme: ColorScheme): any { const content_width = 264 - const ctaButton = + const cta_button = // Copied from welcome screen. FIXME: Move this into a ZDS component interactive({ base: { - background: background(layer), - border: border(layer, "default"), + background: background(theme.middle), + border: border(theme.middle, "default"), corner_radius: 4, margin: { top: 4, @@ -25,22 +24,22 @@ export default function copilot(colorScheme: ColorScheme): any { left: 7, right: 7, }, - ...text(layer, "sans", "default", { size: "sm" }), + ...text(theme.middle, "sans", "default", { size: "sm" }), }, state: { hovered: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), }, }, }) return { - outLinkIcon: interactive({ + out_link_icon: interactive({ base: { icon: svg( - foreground(layer, "variant"), + foreground(theme.middle, "variant"), "icons/link_out_12.svg", 12, 12 @@ -53,21 +52,21 @@ export default function copilot(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.middle, "hovered"), }, }, }, }), modal: { - titleText: { + title_text: { default: { - ...text(layer, "sans", { size: "xs", weight: "bold" }), + ...text(theme.middle, "sans", { size: "xs", weight: "bold" }), }, }, titlebar: { - background: background(colorScheme.lowest), - border: border(layer, "active"), + background: background(theme.lowest), + border: border(theme.middle, "active"), padding: { top: 4, bottom: 4, @@ -76,7 +75,7 @@ export default function copilot(colorScheme: ColorScheme): any { }, }, container: { - background: background(colorScheme.lowest), + background: background(theme.lowest), padding: { top: 0, left: 0, @@ -84,10 +83,10 @@ export default function copilot(colorScheme: ColorScheme): any { bottom: 8, }, }, - closeIcon: interactive({ + close_icon: interactive({ base: { icon: svg( - foreground(layer, "variant"), + foreground(theme.middle, "variant"), "icons/x_mark_8.svg", 8, 8 @@ -108,7 +107,7 @@ export default function copilot(colorScheme: ColorScheme): any { state: { hovered: { icon: svg( - foreground(layer, "on"), + foreground(theme.middle, "on"), "icons/x_mark_8.svg", 8, 8 @@ -116,7 +115,7 @@ export default function copilot(colorScheme: ColorScheme): any { }, clicked: { icon: svg( - foreground(layer, "base"), + foreground(theme.middle, "base"), "icons/x_mark_8.svg", 8, 8 @@ -133,11 +132,11 @@ export default function copilot(colorScheme: ColorScheme): any { auth: { content_width, - ctaButton, + cta_button, header: { icon: svg( - foreground(layer, "default"), + foreground(theme.middle, "default"), "icons/zed_plus_copilot_32.svg", 92, 32 @@ -154,7 +153,7 @@ export default function copilot(colorScheme: ColorScheme): any { prompting: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 6, bottom: 12, @@ -164,19 +163,19 @@ export default function copilot(colorScheme: ColorScheme): any { }, hint: { - ...text(layer, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { size: "xs", color: "#838994" }), margin: { top: 6, bottom: 2, }, }, - deviceCode: { - text: text(layer, "mono", { size: "sm" }), + device_code: { + text: text(theme.middle, "mono", { size: "sm" }), cta: { - ...ctaButton, - background: background(colorScheme.lowest), - border: border(colorScheme.lowest, "inverted"), + ...cta_button, + background: background(theme.lowest), + border: border(theme.lowest, "inverted"), padding: { top: 0, bottom: 0, @@ -189,7 +188,7 @@ export default function copilot(colorScheme: ColorScheme): any { }, }, left: content_width / 2, - leftContainer: { + left_container: { padding: { top: 3, bottom: 3, @@ -198,9 +197,9 @@ export default function copilot(colorScheme: ColorScheme): any { }, }, right: (content_width * 1) / 3, - rightContainer: interactive({ + right_container: interactive({ base: { - border: border(colorScheme.lowest, "inverted", { + border: border(theme.lowest, "inverted", { bottom: false, right: false, top: false, @@ -215,7 +214,7 @@ export default function copilot(colorScheme: ColorScheme): any { }, state: { hovered: { - border: border(layer, "active", { + border: border(theme.middle, "active", { bottom: false, right: false, top: false, @@ -227,9 +226,9 @@ export default function copilot(colorScheme: ColorScheme): any { }, }, - notAuthorized: { + not_authorized: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 16, @@ -240,12 +239,12 @@ export default function copilot(colorScheme: ColorScheme): any { }, warning: { - ...text(layer, "sans", { + ...text(theme.middle, "sans", { size: "xs", - color: foreground(layer, "warning"), + color: foreground(theme.middle, "warning"), }), - border: border(layer, "warning"), - background: background(layer, "warning"), + border: border(theme.middle, "warning"), + background: background(theme.middle, "warning"), corner_radius: 2, padding: { top: 4, @@ -263,7 +262,7 @@ export default function copilot(colorScheme: ColorScheme): any { authorized: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 16, @@ -272,7 +271,7 @@ export default function copilot(colorScheme: ColorScheme): any { }, hint: { - ...text(layer, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { size: "xs", color: "#838994" }), margin: { top: 24, bottom: 4, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 14df1b0bb3953cc5bbd559175bc0d8cc30ffe76b..4e138260131d1f7a7bedff845b811044d35918a2 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -7,17 +7,17 @@ import { foreground, text, } from "./components" -import hoverPopover from "./hover_popover" +import hover_popover from "./hover_popover" -import { buildSyntax } from "../theme/syntax" +import { build_syntax } from "../theme/syntax" import { interactive, toggleable } from "../element" -export default function editor(colorScheme: ColorScheme): any { - const { is_light } = colorScheme +export default function editor(theme: ColorScheme): any { + const { is_light } = theme - const layer = colorScheme.highest + const layer = theme.highest - const autocompleteItem = { + const autocomplete_item = { corner_radius: 6, padding: { bottom: 2, @@ -29,7 +29,7 @@ export default function editor(colorScheme: ColorScheme): any { function diagnostic(layer: Layer, styleSet: StyleSets) { return { - textScaleFactor: 0.857, + text_scale_factor: 0.857, header: { border: border(layer, { top: true, @@ -37,7 +37,7 @@ export default function editor(colorScheme: ColorScheme): any { }, message: { text: text(layer, "sans", styleSet, "default", { size: "sm" }), - highlightText: text(layer, "sans", styleSet, "default", { + highlight_text: text(layer, "sans", styleSet, "default", { size: "sm", weight: "bold", }), @@ -45,16 +45,16 @@ export default function editor(colorScheme: ColorScheme): any { } } - const syntax = buildSyntax(colorScheme) + const syntax = build_syntax(theme) return { - textColor: syntax.primary.color, + text_color: syntax.primary.color, background: background(layer), - activeLineBackground: withOpacity(background(layer, "on"), 0.75), - highlightedLineBackground: background(layer, "on"), + active_line_background: withOpacity(background(layer, "on"), 0.75), + highlighted_line_background: background(layer, "on"), // Inline autocomplete suggestions, Co-pilot suggestions, etc. suggestion: syntax.predictive, - codeActions: { + code_actions: { indicator: toggleable({ base: interactive({ base: { @@ -84,12 +84,12 @@ export default function editor(colorScheme: ColorScheme): any { }, }), - verticalScale: 0.55, + vertical_scale: 0.55, }, folds: { - iconMarginScale: 2.5, - foldedIcon: "icons/chevron_right_8.svg", - foldableIcon: "icons/chevron_down_8.svg", + icon_margin_scale: 2.5, + folded_icon: "icons/chevron_right_8.svg", + foldable_icon: "icons/chevron_down_8.svg", indicator: toggleable({ base: interactive({ base: { @@ -116,20 +116,20 @@ export default function editor(colorScheme: ColorScheme): any { }, }), ellipses: { - textColor: colorScheme.ramps.neutral(0.71).hex(), - corner_radiusFactor: 0.15, + text_color: theme.ramps.neutral(0.71).hex(), + corner_radius_factor: 0.15, background: { // Copied from hover_popover highlight default: { - color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(), + color: theme.ramps.neutral(0.5).alpha(0.0).hex(), }, hovered: { - color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(), + color: theme.ramps.neutral(0.5).alpha(0.5).hex(), }, clicked: { - color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(), + color: theme.ramps.neutral(0.5).alpha(0.7).hex(), }, }, }, @@ -137,14 +137,14 @@ export default function editor(colorScheme: ColorScheme): any { }, diff: { deleted: is_light - ? colorScheme.ramps.red(0.5).hex() - : colorScheme.ramps.red(0.4).hex(), + ? theme.ramps.red(0.5).hex() + : theme.ramps.red(0.4).hex(), modified: is_light - ? colorScheme.ramps.yellow(0.5).hex() - : colorScheme.ramps.yellow(0.5).hex(), + ? theme.ramps.yellow(0.5).hex() + : theme.ramps.yellow(0.5).hex(), inserted: is_light - ? colorScheme.ramps.green(0.4).hex() - : colorScheme.ramps.green(0.5).hex(), + ? theme.ramps.green(0.4).hex() + : theme.ramps.green(0.5).hex(), removedWidthEm: 0.275, widthEm: 0.15, corner_radius: 0.05, @@ -156,7 +156,7 @@ export default function editor(colorScheme: ColorScheme): any { foreground(layer, "accent"), 0.1 ), - documentHighlightWriteBackground: colorScheme.ramps + documentHighlightWriteBackground: theme.ramps .neutral(0.5) .alpha(0.4) .hex(), // TODO: This was blend * 2 @@ -167,98 +167,98 @@ export default function editor(colorScheme: ColorScheme): any { lineNumberActive: foreground(layer), renameFade: 0.6, unnecessaryCodeFade: 0.5, - selection: colorScheme.players[0], - whitespace: colorScheme.ramps.neutral(0.5).hex(), + selection: theme.players[0], + whitespace: theme.ramps.neutral(0.5).hex(), guestSelections: [ - colorScheme.players[1], - colorScheme.players[2], - colorScheme.players[3], - colorScheme.players[4], - colorScheme.players[5], - colorScheme.players[6], - colorScheme.players[7], + theme.players[1], + theme.players[2], + theme.players[3], + theme.players[4], + theme.players[5], + theme.players[6], + theme.players[7], ], autocomplete: { - background: background(colorScheme.middle), + background: background(theme.middle), corner_radius: 8, padding: 4, margin: { left: -14, }, - border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, - matchHighlight: foreground(colorScheme.middle, "accent"), - item: autocompleteItem, + border: border(theme.middle), + shadow: theme.popoverShadow, + matchHighlight: foreground(theme.middle, "accent"), + item: autocomplete_item, hoveredItem: { - ...autocompleteItem, + ...autocomplete_item, matchHighlight: foreground( - colorScheme.middle, + theme.middle, "accent", "hovered" ), - background: background(colorScheme.middle, "hovered"), + background: background(theme.middle, "hovered"), }, selectedItem: { - ...autocompleteItem, + ...autocomplete_item, matchHighlight: foreground( - colorScheme.middle, + theme.middle, "accent", "active" ), - background: background(colorScheme.middle, "active"), + background: background(theme.middle, "active"), }, }, diagnosticHeader: { - background: background(colorScheme.middle), + background: background(theme.middle), icon_widthFactor: 1.5, textScaleFactor: 0.857, - border: border(colorScheme.middle, { + border: border(theme.middle, { bottom: true, top: true, }), code: { - ...text(colorScheme.middle, "mono", { size: "sm" }), + ...text(theme.middle, "mono", { size: "sm" }), margin: { left: 10, }, }, source: { - text: text(colorScheme.middle, "sans", { + text: text(theme.middle, "sans", { size: "sm", weight: "bold", }), }, message: { - highlightText: text(colorScheme.middle, "sans", { + highlightText: text(theme.middle, "sans", { size: "sm", weight: "bold", }), - text: text(colorScheme.middle, "sans", { size: "sm" }), + text: text(theme.middle, "sans", { size: "sm" }), }, }, diagnosticPathHeader: { - background: background(colorScheme.middle), + background: background(theme.middle), textScaleFactor: 0.857, - filename: text(colorScheme.middle, "mono", { size: "sm" }), + filename: text(theme.middle, "mono", { size: "sm" }), path: { - ...text(colorScheme.middle, "mono", { size: "sm" }), + ...text(theme.middle, "mono", { size: "sm" }), margin: { left: 12, }, }, }, - errorDiagnostic: diagnostic(colorScheme.middle, "negative"), - warningDiagnostic: diagnostic(colorScheme.middle, "warning"), - informationDiagnostic: diagnostic(colorScheme.middle, "accent"), - hintDiagnostic: diagnostic(colorScheme.middle, "warning"), - invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"), - hoverPopover: hoverPopover(colorScheme), + errorDiagnostic: diagnostic(theme.middle, "negative"), + warningDiagnostic: diagnostic(theme.middle, "warning"), + informationDiagnostic: diagnostic(theme.middle, "accent"), + hintDiagnostic: diagnostic(theme.middle, "warning"), + invalidErrorDiagnostic: diagnostic(theme.middle, "base"), + invalidHintDiagnostic: diagnostic(theme.middle, "base"), + invalidInformationDiagnostic: diagnostic(theme.middle, "base"), + invalidWarningDiagnostic: diagnostic(theme.middle, "base"), + hover_popover: hover_popover(theme), linkDefinition: { - color: syntax.linkUri.color, - underline: syntax.linkUri.underline, + color: syntax.link_uri.color, + underline: syntax.link_uri.underline, }, jumpIcon: interactive({ base: { @@ -299,14 +299,14 @@ export default function editor(colorScheme: ColorScheme): any { }, git: { deleted: is_light - ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), + ? withOpacity(theme.ramps.red(0.5).hex(), 0.8) + : withOpacity(theme.ramps.red(0.4).hex(), 0.8), modified: is_light - ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), + ? withOpacity(theme.ramps.yellow(0.5).hex(), 0.8) + : withOpacity(theme.ramps.yellow(0.4).hex(), 0.8), inserted: is_light - ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), + ? withOpacity(theme.ramps.green(0.5).hex(), 0.8) + : withOpacity(theme.ramps.green(0.4).hex(), 0.8), }, }, compositionMark: { diff --git a/styles/src/style_tree/incoming_call_notification.ts b/styles/src/style_tree/incoming_call_notification.ts index 2d76da7de82eef92a158fb5d2fa2870c085f547b..2c4fa21867e9cfe34da0044eaa5d1bdc855cf1f4 100644 --- a/styles/src/style_tree/incoming_call_notification.ts +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -39,14 +39,14 @@ export default function incoming_call_notification( border: border(layer, { left: true, bottom: true }), ...text(layer, "sans", "positive", { size: "xs", - weight: "extra_bold", + weight: "bold", }), }, declineButton: { border: border(layer, { left: true }), ...text(layer, "sans", "negative", { size: "xs", - weight: "extra_bold", + weight: "bold", }), }, } diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index ac5c577e2114e4bad8aa2d3513c896aeec24fcc0..589e120e381075fe2b12303a0c5a9b798212cf5a 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -48,7 +48,7 @@ export default function project_panel(theme: ColorScheme): any { background: background(layer), iconColor: foreground(layer, "variant"), iconSize: 7, - iconSpacing: 5, + icon_spacing: 5, text: text(layer, "mono", "variant", { size: "sm" }), status: { ...git_status, diff --git a/styles/src/style_tree/project_shared_notification.ts b/styles/src/style_tree/project_shared_notification.ts index 8437ab6c1bb628206bd9367691ea448f189a7873..6fe8170a3ce3815c8eb6118c1309a671cfe0f2c4 100644 --- a/styles/src/style_tree/project_shared_notification.ts +++ b/styles/src/style_tree/project_shared_notification.ts @@ -40,14 +40,14 @@ export default function project_shared_notification( border: border(layer, { left: true, bottom: true }), ...text(layer, "sans", "accent", { size: "xs", - weight: "extra_bold", + weight: "bold", }), }, dismissButton: { border: border(layer, { left: true }), ...text(layer, "sans", "variant", { size: "xs", - weight: "extra_bold", + weight: "bold", }), }, } diff --git a/styles/src/style_tree/status_bar.ts b/styles/src/style_tree/status_bar.ts index fb1c572615152159c63b3739324ed97f1c585103..d67634d5a80436cd4e452619ef971032fb1bbf8c 100644 --- a/styles/src/style_tree/status_bar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -41,7 +41,7 @@ export default function status_bar(colorScheme: ColorScheme): any { lspStatus: interactive({ base: { ...diagnosticStatusContainer, - iconSpacing: 4, + icon_spacing: 4, icon_width: 14, height: 18, message: text(layer, "sans"), @@ -65,7 +65,7 @@ export default function status_bar(colorScheme: ColorScheme): any { base: { height: 20, icon_width: 16, - iconSpacing: 2, + icon_spacing: 2, summarySpacing: 6, text: text(layer, "sans", { size: "sm" }), iconColorOk: foreground(layer, "variant"), diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index 5d214abdebe5a93cdf1d25e7b0ff31168fe5cdd2..1b61849d50d168d2ba0a3fcfabec0069578e098b 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -1,5 +1,5 @@ import deepmerge from "deepmerge" -import { FontWeight, fontWeights } from "../common" +import { FontWeight, font_weights } from "../common" import { ColorScheme } from "./color_scheme" import chroma from "chroma-js" @@ -22,8 +22,8 @@ export interface Syntax { emphasis: SyntaxHighlightStyle "emphasis.strong": SyntaxHighlightStyle title: SyntaxHighlightStyle - linkUri: SyntaxHighlightStyle - linkText: SyntaxHighlightStyle + link_uri: SyntaxHighlightStyle + link_text: SyntaxHighlightStyle /** md: indented_code_block, fenced_code_block, code_span */ "text.literal": SyntaxHighlightStyle @@ -116,13 +116,13 @@ export interface Syntax { export type ThemeSyntax = Partial -const defaultSyntaxHighlightStyle: Omit = { +const default_syntaxHighlightStyle: Omit = { weight: "normal", underline: false, italic: false, } -function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { +function build_default_syntax(color_scheme: ColorScheme): Syntax { // Make a temporary object that is allowed to be missing // the "color" property for each style const syntax: { @@ -132,7 +132,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { // then spread the default to each style for (const key of Object.keys({} as Syntax)) { syntax[key as keyof Syntax] = { - ...defaultSyntaxHighlightStyle, + ...default_syntaxHighlightStyle, } } @@ -140,35 +140,35 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { // predictive color distinct from any other color in the theme const predictive = chroma .mix( - colorScheme.ramps.neutral(0.4).hex(), - colorScheme.ramps.blue(0.4).hex(), + color_scheme.ramps.neutral(0.4).hex(), + color_scheme.ramps.blue(0.4).hex(), 0.45, "lch" ) .hex() const color = { - primary: colorScheme.ramps.neutral(1).hex(), - comment: colorScheme.ramps.neutral(0.71).hex(), - punctuation: colorScheme.ramps.neutral(0.86).hex(), + primary: color_scheme.ramps.neutral(1).hex(), + comment: color_scheme.ramps.neutral(0.71).hex(), + punctuation: color_scheme.ramps.neutral(0.86).hex(), predictive: predictive, - emphasis: colorScheme.ramps.blue(0.5).hex(), - string: colorScheme.ramps.orange(0.5).hex(), - function: colorScheme.ramps.yellow(0.5).hex(), - type: colorScheme.ramps.cyan(0.5).hex(), - constructor: colorScheme.ramps.blue(0.5).hex(), - variant: colorScheme.ramps.blue(0.5).hex(), - property: colorScheme.ramps.blue(0.5).hex(), - enum: colorScheme.ramps.orange(0.5).hex(), - operator: colorScheme.ramps.orange(0.5).hex(), - number: colorScheme.ramps.green(0.5).hex(), - boolean: colorScheme.ramps.green(0.5).hex(), - constant: colorScheme.ramps.green(0.5).hex(), - keyword: colorScheme.ramps.blue(0.5).hex(), + emphasis: color_scheme.ramps.blue(0.5).hex(), + string: color_scheme.ramps.orange(0.5).hex(), + function: color_scheme.ramps.yellow(0.5).hex(), + type: color_scheme.ramps.cyan(0.5).hex(), + constructor: color_scheme.ramps.blue(0.5).hex(), + variant: color_scheme.ramps.blue(0.5).hex(), + property: color_scheme.ramps.blue(0.5).hex(), + enum: color_scheme.ramps.orange(0.5).hex(), + operator: color_scheme.ramps.orange(0.5).hex(), + number: color_scheme.ramps.green(0.5).hex(), + boolean: color_scheme.ramps.green(0.5).hex(), + constant: color_scheme.ramps.green(0.5).hex(), + keyword: color_scheme.ramps.blue(0.5).hex(), } // Then assign colors and use Syntax to enforce each style getting it's own color - const defaultSyntax: Syntax = { + const default_syntax: Syntax = { ...syntax, comment: { color: color.comment, @@ -188,18 +188,18 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { }, "emphasis.strong": { color: color.emphasis, - weight: fontWeights.bold, + weight: font_weights.bold, }, title: { color: color.primary, - weight: fontWeights.bold, + weight: font_weights.bold, }, - linkUri: { - color: colorScheme.ramps.green(0.5).hex(), + link_uri: { + color: color_scheme.ramps.green(0.5).hex(), underline: true, }, - linkText: { - color: colorScheme.ramps.orange(0.5).hex(), + link_text: { + color: color_scheme.ramps.orange(0.5).hex(), italic: true, }, "text.literal": { @@ -215,7 +215,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.punctuation, }, "punctuation.special": { - color: colorScheme.ramps.neutral(0.86).hex(), + color: color_scheme.ramps.neutral(0.86).hex(), }, "punctuation.list_marker": { color: color.punctuation, @@ -236,10 +236,10 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.string, }, constructor: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, variant: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, type: { color: color.type, @@ -248,16 +248,16 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.primary, }, label: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, tag: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, attribute: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, property: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, constant: { color: color.constant, @@ -288,17 +288,17 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { }, } - return defaultSyntax + return default_syntax } -function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax { - if (!colorScheme.syntax) { - return defaultSyntax +function merge_syntax(default_syntax: Syntax, color_scheme: ColorScheme): Syntax { + if (!color_scheme.syntax) { + return default_syntax } return deepmerge>( - defaultSyntax, - colorScheme.syntax, + default_syntax, + color_scheme.syntax, { arrayMerge: (destinationArray, sourceArray) => [ ...destinationArray, @@ -308,10 +308,10 @@ function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax { ) } -export function buildSyntax(colorScheme: ColorScheme): Syntax { - const defaultSyntax: Syntax = buildDefaultSyntax(colorScheme) +export function build_syntax(color_scheme: ColorScheme): Syntax { + const default_syntax: Syntax = build_default_syntax(color_scheme) - const syntax = mergeSyntax(defaultSyntax, colorScheme) + const syntax = merge_syntax(default_syntax, color_scheme) return syntax } diff --git a/styles/src/themes/gruvbox/gruvbox-common.ts b/styles/src/themes/gruvbox/gruvbox-common.ts index 37850fe019c1bc186b21de6a59a2bc930a139eec..6a911ce731d781d5ea614618928d5bc131ffe38e 100644 --- a/styles/src/themes/gruvbox/gruvbox-common.ts +++ b/styles/src/themes/gruvbox/gruvbox-common.ts @@ -238,8 +238,8 @@ const buildVariant = (variant: Variant): ThemeConfig => { variable: { color: colors.blue }, property: { color: neutral[isLight ? 0 : 8] }, embedded: { color: colors.aqua }, - linkText: { color: colors.aqua }, - linkUri: { color: colors.purple }, + link_text: { color: colors.aqua }, + link_uri: { color: colors.purple }, title: { color: colors.green }, } diff --git a/styles/src/themes/one/one-dark.ts b/styles/src/themes/one/one-dark.ts index 69a5bd55755a55cce1fd6128a5a8922d3ce7cf31..b8456603ce6e5c02c2a476e7861eb9a2fd43713b 100644 --- a/styles/src/themes/one/one-dark.ts +++ b/styles/src/themes/one/one-dark.ts @@ -1,6 +1,6 @@ import { chroma, - fontWeights, + font_weights, colorRamp, ThemeAppearance, ThemeLicenseType, @@ -57,8 +57,8 @@ export const theme: ThemeConfig = { "emphasis.strong": { color: color.orange }, function: { color: color.blue }, keyword: { color: color.purple }, - linkText: { color: color.blue, italic: false }, - linkUri: { color: color.teal }, + link_text: { color: color.blue, italic: false }, + link_uri: { color: color.teal }, number: { color: color.orange }, constant: { color: color.yellow }, operator: { color: color.teal }, @@ -68,7 +68,7 @@ export const theme: ThemeConfig = { "punctuation.list_marker": { color: color.red }, "punctuation.special": { color: color.darkRed }, string: { color: color.green }, - title: { color: color.red, weight: fontWeights.normal }, + title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, type: { color: color.teal }, "variable.special": { color: color.orange }, diff --git a/styles/src/themes/one/one-light.ts b/styles/src/themes/one/one-light.ts index 9123c8879d1bcc11f9258fc538aa4b377c2a3ec5..e14862f423b67ece038fbd09fd3f651761b0bad6 100644 --- a/styles/src/themes/one/one-light.ts +++ b/styles/src/themes/one/one-light.ts @@ -1,6 +1,6 @@ import { chroma, - fontWeights, + font_weights, colorRamp, ThemeAppearance, ThemeLicenseType, @@ -59,8 +59,8 @@ export const theme: ThemeConfig = { "emphasis.strong": { color: color.orange }, function: { color: color.blue }, keyword: { color: color.purple }, - linkText: { color: color.blue }, - linkUri: { color: color.teal }, + link_text: { color: color.blue }, + link_uri: { color: color.teal }, number: { color: color.orange }, operator: { color: color.teal }, primary: { color: color.black }, @@ -69,7 +69,7 @@ export const theme: ThemeConfig = { "punctuation.list_marker": { color: color.red }, "punctuation.special": { color: color.darkRed }, string: { color: color.green }, - title: { color: color.red, weight: fontWeights.normal }, + title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, type: { color: color.teal }, "variable.special": { color: color.orange }, diff --git a/styles/src/themes/rose-pine/common.ts b/styles/src/themes/rose-pine/common.ts index 146305890b98e136acbfd04955b28c7f233a898e..30906078ee1f8632099d8bd10f2f6aa5f0d639d2 100644 --- a/styles/src/themes/rose-pine/common.ts +++ b/styles/src/themes/rose-pine/common.ts @@ -69,7 +69,7 @@ export const syntax = (c: typeof color.default): Partial => { tag: { color: c.foam }, "function.method": { color: c.rose }, title: { color: c.gold }, - linkText: { color: c.foam, italic: false }, - linkUri: { color: c.rose }, + link_text: { color: c.foam, italic: false }, + link_uri: { color: c.rose }, } } From 17f2fed3c8362d9b2290a22da83a812d41a43536 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 02:16:21 -0400 Subject: [PATCH 032/169] WIP snake_case 3/? --- styles/src/build_licenses.ts | 2 +- styles/src/build_themes.ts | 4 +- styles/src/build_tokens.ts | 4 +- styles/src/style_tree/contacts_popover.ts | 2 +- styles/src/style_tree/context_menu.ts | 2 +- styles/src/style_tree/editor.ts | 2 +- styles/src/style_tree/hover_popover.ts | 2 +- styles/src/style_tree/picker.ts | 2 +- styles/src/style_tree/tab_bar.ts | 2 +- .../src/style_tree/toolbar_dropdown_menu.ts | 2 +- styles/src/style_tree/tooltip.ts | 2 +- styles/src/style_tree/welcome.ts | 2 +- styles/src/style_tree/workspace.ts | 4 +- styles/src/theme/color_scheme.ts | 164 +++++++++--------- styles/src/theme/ramps.ts | 36 ++-- styles/src/theme/syntax.ts | 4 +- styles/src/theme/theme_config.ts | 12 +- styles/src/theme/tokens/color_scheme.ts | 48 ++--- styles/src/themes/andromeda/andromeda.ts | 26 +-- .../src/themes/atelier/atelier-cave-dark.ts | 34 ++-- .../src/themes/atelier/atelier-cave-light.ts | 34 ++-- .../src/themes/atelier/atelier-dune-dark.ts | 34 ++-- .../src/themes/atelier/atelier-dune-light.ts | 34 ++-- .../themes/atelier/atelier-estuary-dark.ts | 34 ++-- .../themes/atelier/atelier-estuary-light.ts | 34 ++-- .../src/themes/atelier/atelier-forest-dark.ts | 34 ++-- .../themes/atelier/atelier-forest-light.ts | 34 ++-- .../src/themes/atelier/atelier-heath-dark.ts | 34 ++-- .../src/themes/atelier/atelier-heath-light.ts | 34 ++-- .../themes/atelier/atelier-lakeside-dark.ts | 34 ++-- .../themes/atelier/atelier-lakeside-light.ts | 34 ++-- .../themes/atelier/atelier-plateau-dark.ts | 34 ++-- .../themes/atelier/atelier-plateau-light.ts | 34 ++-- .../themes/atelier/atelier-savanna-dark.ts | 34 ++-- .../themes/atelier/atelier-savanna-light.ts | 34 ++-- .../themes/atelier/atelier-seaside-dark.ts | 34 ++-- .../themes/atelier/atelier-seaside-light.ts | 34 ++-- .../atelier/atelier-sulphurpool-dark.ts | 34 ++-- .../atelier/atelier-sulphurpool-light.ts | 34 ++-- styles/src/themes/atelier/common.ts | 6 +- styles/src/themes/ayu/ayu-dark.ts | 12 +- styles/src/themes/ayu/ayu-light.ts | 12 +- styles/src/themes/ayu/ayu-mirage.ts | 12 +- styles/src/themes/ayu/common.ts | 26 +-- styles/src/themes/gruvbox/gruvbox-common.ts | 90 +++++----- .../src/themes/gruvbox/gruvbox-dark-hard.ts | 2 +- .../src/themes/gruvbox/gruvbox-dark-soft.ts | 2 +- styles/src/themes/gruvbox/gruvbox-dark.ts | 2 +- .../src/themes/gruvbox/gruvbox-light-hard.ts | 2 +- .../src/themes/gruvbox/gruvbox-light-soft.ts | 2 +- styles/src/themes/gruvbox/gruvbox-light.ts | 2 +- styles/src/themes/index.ts | 148 ++++++++-------- styles/src/themes/one/one-dark.ts | 30 ++-- styles/src/themes/one/one-light.ts | 30 ++-- styles/src/themes/rose-pine/common.ts | 18 +- styles/src/themes/rose-pine/rose-pine-dawn.ts | 28 +-- styles/src/themes/rose-pine/rose-pine-moon.ts | 28 +-- styles/src/themes/rose-pine/rose-pine.ts | 28 +-- styles/src/themes/sandcastle/sandcastle.ts | 26 +-- styles/src/themes/solarized/solarized.ts | 34 ++-- styles/src/themes/summercamp/summercamp.ts | 26 +-- styles/src/utils/snake_case.ts | 4 +- 62 files changed, 786 insertions(+), 786 deletions(-) diff --git a/styles/src/build_licenses.ts b/styles/src/build_licenses.ts index 88cefe15c7beaa913b5721a0c1064ff7d5ceb8e8..9cfaafdb750ce42207b2cfab5b2048c4ed72a50d 100644 --- a/styles/src/build_licenses.ts +++ b/styles/src/build_licenses.ts @@ -30,7 +30,7 @@ function generate_license_file(themes: ThemeConfig[]) { check_licenses(themes) for (const theme of themes) { const license_text = fs.readFileSync(theme.licenseFile).toString() - write_license(theme.name, license_text, theme.licenseUrl) + write_license(theme.name, license_text, theme.license_url) } } diff --git a/styles/src/build_themes.ts b/styles/src/build_themes.ts index 132b91e5826043b2d221abb95376ad0ccb8c0230..98ab8d27081dd0c0d7d58c758b343cb0f031c425 100644 --- a/styles/src/build_themes.ts +++ b/styles/src/build_themes.ts @@ -2,7 +2,7 @@ import * as fs from "fs" import { tmpdir } from "os" import * as path from "path" import app from "./style_tree/app" -import { ColorScheme, createColorScheme } from "./theme/color_scheme" +import { ColorScheme, create_color_scheme } from "./theme/color_scheme" import snakeCase from "./utils/snake_case" import { themes } from "./themes" @@ -36,7 +36,7 @@ function write_themes(color_schemes: ColorScheme[], output_directory: string) { } const color_schemes: ColorScheme[] = themes.map((theme) => - createColorScheme(theme) + create_color_scheme(theme) ) // Write new themes to theme directory diff --git a/styles/src/build_tokens.ts b/styles/src/build_tokens.ts index 4e420d679a0cd317ad4224f1af1fd363a05f900b..09eed6a7b956e0f2e53865bbcf6bd470a529b72b 100644 --- a/styles/src/build_tokens.ts +++ b/styles/src/build_tokens.ts @@ -1,6 +1,6 @@ import * as fs from "fs" import * as path from "path" -import { ColorScheme, createColorScheme } from "./common" +import { ColorScheme, create_color_scheme } from "./common" import { themes } from "./themes" import { slugify } from "./utils/slugify" import { colorSchemeTokens as color_scheme_tokens } from "./theme/tokens/color_scheme" @@ -81,7 +81,7 @@ function write_tokens(themes: ColorScheme[], tokens_directory: string) { } const all_themes: ColorScheme[] = themes.map((theme) => - createColorScheme(theme) + create_color_scheme(theme) ) write_tokens(all_themes, TOKENS_DIRECTORY) diff --git a/styles/src/style_tree/contacts_popover.ts b/styles/src/style_tree/contacts_popover.ts index 2018da6b845d29dfc147c7a346d52161c8d08065..4e3f8899e046bc27ddcc394c42e845278b73a51d 100644 --- a/styles/src/style_tree/contacts_popover.ts +++ b/styles/src/style_tree/contacts_popover.ts @@ -6,7 +6,7 @@ export default function contacts_popover(theme: ColorScheme): any { background: background(theme.middle), corner_radius: 6, padding: { top: 6, bottom: 6 }, - shadow: theme.popoverShadow, + shadow: theme.popover_shadow, border: border(theme.middle), width: 300, height: 400, diff --git a/styles/src/style_tree/context_menu.ts b/styles/src/style_tree/context_menu.ts index df765e0a4a9daabd2f8074815da7ad71a3fb2f35..f111225c947af39b7fb25234de3196b61fca5735 100644 --- a/styles/src/style_tree/context_menu.ts +++ b/styles/src/style_tree/context_menu.ts @@ -7,7 +7,7 @@ export default function context_menu(theme: ColorScheme): any { background: background(theme.middle), corner_radius: 10, padding: 4, - shadow: theme.popoverShadow, + shadow: theme.popover_shadow, border: border(theme.middle), keystroke_margin: 30, item: toggleable({ diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 4e138260131d1f7a7bedff845b811044d35918a2..95760ce1d0228c28e93cd7fc0c6993d75de1d8c9 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -186,7 +186,7 @@ export default function editor(theme: ColorScheme): any { left: -14, }, border: border(theme.middle), - shadow: theme.popoverShadow, + shadow: theme.popover_shadow, matchHighlight: foreground(theme.middle, "accent"), item: autocomplete_item, hoveredItem: { diff --git a/styles/src/style_tree/hover_popover.ts b/styles/src/style_tree/hover_popover.ts index 42c4f72a263469369fd98ea0ae8f65270767a9ed..28f5b1740018e162ba0741e400e76cbc554b1fc8 100644 --- a/styles/src/style_tree/hover_popover.ts +++ b/styles/src/style_tree/hover_popover.ts @@ -12,7 +12,7 @@ export default function hover_popover(colorScheme: ColorScheme): any { top: 4, bottom: 4, }, - shadow: colorScheme.popoverShadow, + shadow: colorScheme.popover_shadow, border: border(layer), margin: { left: -8, diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index 99bd716c16133be1e17b07dff16a1b9788c8da8f..22b526f1831c4486cc9251e4a827807b6cbe7d19 100644 --- a/styles/src/style_tree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -8,7 +8,7 @@ export default function picker(colorScheme: ColorScheme): any { const container = { background: background(layer), border: border(layer), - shadow: colorScheme.modalShadow, + shadow: colorScheme.modal_shadow, corner_radius: 12, padding: { bottom: 4, diff --git a/styles/src/style_tree/tab_bar.ts b/styles/src/style_tree/tab_bar.ts index d8323809edd2118876484cfa8f1f77f5b3674335..63f0b213a6c44307bcc2509401c3af60bc850541 100644 --- a/styles/src/style_tree/tab_bar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -73,7 +73,7 @@ export default function tab_bar(colorScheme: ColorScheme): any { ...activePaneActiveTab, background: withOpacity(tab.background, 0.9), border: undefined as any, - shadow: colorScheme.popoverShadow, + shadow: colorScheme.popover_shadow, } return { diff --git a/styles/src/style_tree/toolbar_dropdown_menu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts index 5d0bf89d93eb27f82b5f94e7b9450129ea15eee5..51e62db822edc8d0da82378ee25b5f3e03f17287 100644 --- a/styles/src/style_tree/toolbar_dropdown_menu.ts +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -8,7 +8,7 @@ export default function dropdown_menu(colorScheme: ColorScheme): any { rowHeight: 30, background: background(layer), border: border(layer), - shadow: colorScheme.popoverShadow, + shadow: colorScheme.popover_shadow, header: interactive({ base: { ...text(layer, "sans", { size: "sm" }), diff --git a/styles/src/style_tree/tooltip.ts b/styles/src/style_tree/tooltip.ts index a872477f4987680207b75cbe2bed6f6329e300af..ea890232b5d45938cc0411e5cf087674deff5468 100644 --- a/styles/src/style_tree/tooltip.ts +++ b/styles/src/style_tree/tooltip.ts @@ -8,7 +8,7 @@ export default function tooltip(colorScheme: ColorScheme): any { border: border(layer), padding: { top: 4, bottom: 4, left: 8, right: 8 }, margin: { top: 6, left: 6 }, - shadow: colorScheme.popoverShadow, + shadow: colorScheme.popover_shadow, corner_radius: 6, text: text(layer, "sans", { size: "xs" }), keystroke: { diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts index 5d0bc90a00d1e4be6d7c7cfb4f2553cf2b8739cd..9ae9716f66b1d72cb9aab33c830b207ba53f352f 100644 --- a/styles/src/style_tree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -21,7 +21,7 @@ export default function welcome(colorScheme: ColorScheme): any { top: 3, bottom: 3, }, - // shadow: colorScheme.popoverShadow, + // shadow: colorScheme.popover_shadow, border: border(layer), margin: { right: 8, diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index 2ba0281b17ef6ae9976a88d77842245512b75252..8c60c9d402fe9000745b14f7b31bc0daf6246b25 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -102,7 +102,7 @@ export default function workspace(colorScheme: ColorScheme): any { }, zoomedPaneForeground: { margin: 16, - shadow: colorScheme.modalShadow, + shadow: colorScheme.modal_shadow, border: border(colorScheme.lowest, { overlay: true }), }, zoomedPanelForeground: { @@ -189,7 +189,7 @@ export default function workspace(colorScheme: ColorScheme): any { corner_radius: 6, padding: 12, border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, + shadow: colorScheme.popover_shadow, }, notifications: { width: 400, diff --git a/styles/src/theme/color_scheme.ts b/styles/src/theme/color_scheme.ts index 5d62bcffdbfe7eebd27b37995153df508437a2e4..933c6160535386dea3f7b3b517c3d95b234cbcbe 100644 --- a/styles/src/theme/color_scheme.ts +++ b/styles/src/theme/color_scheme.ts @@ -6,7 +6,7 @@ import { ThemeAppearance, ThemeConfigInputColors, } from "./theme_config" -import { getRamps } from "./ramps" +import { get_ramps } from "./ramps" export interface ColorScheme { name: string @@ -18,8 +18,8 @@ export interface ColorScheme { ramps: RampSet - popoverShadow: Shadow - modalShadow: Shadow + popover_shadow: Shadow + modal_shadow: Shadow players: Players syntax?: Partial @@ -105,37 +105,37 @@ export interface Style { foreground: string } -export function createColorScheme(theme: ThemeConfig): ColorScheme { +export function create_color_scheme(theme: ThemeConfig): ColorScheme { const { name, appearance, - inputColor, + input_color, override: { syntax }, } = theme - const isLight = appearance === ThemeAppearance.Light - const colorRamps: ThemeConfigInputColors = inputColor + const is_light = appearance === ThemeAppearance.Light + const color_ramps: ThemeConfigInputColors = input_color - // Chromajs scales from 0 to 1 flipped if isLight is true - const ramps = getRamps(isLight, colorRamps) - const lowest = lowestLayer(ramps) - const middle = middleLayer(ramps) - const highest = highestLayer(ramps) + // Chromajs scales from 0 to 1 flipped if is_light is true + const ramps = get_ramps(is_light, color_ramps) + const lowest = lowest_layer(ramps) + const middle = middle_layer(ramps) + const highest = highest_layer(ramps) - const popoverShadow = { + const popover_shadow = { blur: 4, color: ramps - .neutral(isLight ? 7 : 0) + .neutral(is_light ? 7 : 0) .darken() .alpha(0.2) .hex(), // TODO used blend previously. Replace with something else offset: [1, 2], } - const modalShadow = { + const modal_shadow = { blur: 16, color: ramps - .neutral(isLight ? 7 : 0) + .neutral(is_light ? 7 : 0) .darken() .alpha(0.2) .hex(), // TODO used blend previously. Replace with something else @@ -155,7 +155,7 @@ export function createColorScheme(theme: ThemeConfig): ColorScheme { return { name, - is_light: isLight, + is_light, ramps, @@ -163,8 +163,8 @@ export function createColorScheme(theme: ThemeConfig): ColorScheme { middle, highest, - popoverShadow, - modalShadow, + popover_shadow, + modal_shadow, players, syntax, @@ -178,105 +178,105 @@ function player(ramp: Scale): Player { } } -function lowestLayer(ramps: RampSet): Layer { +function lowest_layer(ramps: RampSet): Layer { return { - base: buildStyleSet(ramps.neutral, 0.2, 1), - variant: buildStyleSet(ramps.neutral, 0.2, 0.7), - on: buildStyleSet(ramps.neutral, 0.1, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), + base: build_style_set(ramps.neutral, 0.2, 1), + variant: build_style_set(ramps.neutral, 0.2, 0.7), + on: build_style_set(ramps.neutral, 0.1, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), } } -function middleLayer(ramps: RampSet): Layer { +function middle_layer(ramps: RampSet): Layer { return { - base: buildStyleSet(ramps.neutral, 0.1, 1), - variant: buildStyleSet(ramps.neutral, 0.1, 0.7), - on: buildStyleSet(ramps.neutral, 0, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), + base: build_style_set(ramps.neutral, 0.1, 1), + variant: build_style_set(ramps.neutral, 0.1, 0.7), + on: build_style_set(ramps.neutral, 0, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), } } -function highestLayer(ramps: RampSet): Layer { +function highest_layer(ramps: RampSet): Layer { return { - base: buildStyleSet(ramps.neutral, 0, 1), - variant: buildStyleSet(ramps.neutral, 0, 0.7), - on: buildStyleSet(ramps.neutral, 0.1, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), + base: build_style_set(ramps.neutral, 0, 1), + variant: build_style_set(ramps.neutral, 0, 0.7), + on: build_style_set(ramps.neutral, 0.1, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), } } -function buildStyleSet( +function build_style_set( ramp: Scale, - backgroundBase: number, - foregroundBase: number, + background_base: number, + foreground_base: number, step = 0.08 ): StyleSet { - const styleDefinitions = buildStyleDefinition( - backgroundBase, - foregroundBase, + const style_definitions = build_style_definition( + background_base, + foreground_base, step ) - function colorString(indexOrColor: number | Color): string { - if (typeof indexOrColor === "number") { - return ramp(indexOrColor).hex() + function color_string(index_or_color: number | Color): string { + if (typeof index_or_color === "number") { + return ramp(index_or_color).hex() } else { - return indexOrColor.hex() + return index_or_color.hex() } } - function buildStyle(style: Styles): Style { + function build_style(style: Styles): Style { return { - background: colorString(styleDefinitions.background[style]), - border: colorString(styleDefinitions.border[style]), - foreground: colorString(styleDefinitions.foreground[style]), + background: color_string(style_definitions.background[style]), + border: color_string(style_definitions.border[style]), + foreground: color_string(style_definitions.foreground[style]), } } return { - default: buildStyle("default"), - hovered: buildStyle("hovered"), - pressed: buildStyle("pressed"), - active: buildStyle("active"), - disabled: buildStyle("disabled"), - inverted: buildStyle("inverted"), + default: build_style("default"), + hovered: build_style("hovered"), + pressed: build_style("pressed"), + active: build_style("active"), + disabled: build_style("disabled"), + inverted: build_style("inverted"), } } -function buildStyleDefinition(bgBase: number, fgBase: number, step = 0.08) { +function build_style_definition(bg_base: number, fg_base: number, step = 0.08) { return { background: { - default: bgBase, - hovered: bgBase + step, - pressed: bgBase + step * 1.5, - active: bgBase + step * 2.2, - disabled: bgBase, - inverted: fgBase + step * 6, + default: bg_base, + hovered: bg_base + step, + pressed: bg_base + step * 1.5, + active: bg_base + step * 2.2, + disabled: bg_base, + inverted: fg_base + step * 6, }, border: { - default: bgBase + step * 1, - hovered: bgBase + step, - pressed: bgBase + step, - active: bgBase + step * 3, - disabled: bgBase + step * 0.5, - inverted: bgBase - step * 3, + default: bg_base + step * 1, + hovered: bg_base + step, + pressed: bg_base + step, + active: bg_base + step * 3, + disabled: bg_base + step * 0.5, + inverted: bg_base - step * 3, }, foreground: { - default: fgBase, - hovered: fgBase, - pressed: fgBase, - active: fgBase + step * 6, - disabled: bgBase + step * 4, - inverted: bgBase + step * 2, + default: fg_base, + hovered: fg_base, + pressed: fg_base, + active: fg_base + step * 6, + disabled: bg_base + step * 4, + inverted: bg_base + step * 2, }, } } diff --git a/styles/src/theme/ramps.ts b/styles/src/theme/ramps.ts index 98a73ef5bf0c768b08159ad9bca44ea19510a6b6..118d0c727456f5369abbd1ca3bb2cbd016921fac 100644 --- a/styles/src/theme/ramps.ts +++ b/styles/src/theme/ramps.ts @@ -5,10 +5,10 @@ import { ThemeConfigInputColorsKeys, } from "./theme_config" -export function colorRamp(color: Color): Scale { - const endColor = color.desaturate(1).brighten(5) - const startColor = color.desaturate(1).darken(4) - return chroma.scale([startColor, color, endColor]).mode("lab") +export function color_ramp(color: Color): Scale { + const end_color = color.desaturate(1).brighten(5) + const start_color = color.desaturate(1).darken(4) + return chroma.scale([start_color, color, end_color]).mode("lab") } /** @@ -18,29 +18,29 @@ export function colorRamp(color: Color): Scale { theme so that we don't modify the passed in ramps. This combined with an error in the type definitions for chroma js means we have to cast the colors function to any in order to get the colors back out from the original ramps. - * @param isLight - * @param colorRamps + * @param is_light + * @param color_ramps * @returns */ -export function getRamps( - isLight: boolean, - colorRamps: ThemeConfigInputColors +export function get_ramps( + is_light: boolean, + color_ramps: ThemeConfigInputColors ): RampSet { const ramps: RampSet = {} as any // eslint-disable-line @typescript-eslint/no-explicit-any - const colorsKeys = Object.keys(colorRamps) as ThemeConfigInputColorsKeys[] + const color_keys = Object.keys(color_ramps) as ThemeConfigInputColorsKeys[] - if (isLight) { - for (const rampName of colorsKeys) { - ramps[rampName] = chroma.scale( - colorRamps[rampName].colors(100).reverse() + if (is_light) { + for (const ramp_name of color_keys) { + ramps[ramp_name] = chroma.scale( + color_ramps[ramp_name].colors(100).reverse() ) } - ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse()) + ramps.neutral = chroma.scale(color_ramps.neutral.colors(100).reverse()) } else { - for (const rampName of colorsKeys) { - ramps[rampName] = chroma.scale(colorRamps[rampName].colors(100)) + for (const ramp_name of color_keys) { + ramps[ramp_name] = chroma.scale(color_ramps[ramp_name].colors(100)) } - ramps.neutral = chroma.scale(colorRamps.neutral.colors(100)) + ramps.neutral = chroma.scale(color_ramps.neutral.colors(100)) } return ramps diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index 1b61849d50d168d2ba0a3fcfabec0069578e098b..a8bf807ee0a4a533eabc971e2505e3de42e5a854 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -116,7 +116,7 @@ export interface Syntax { export type ThemeSyntax = Partial -const default_syntaxHighlightStyle: Omit = { +const default_syntax_highlight_style: Omit = { weight: "normal", underline: false, italic: false, @@ -132,7 +132,7 @@ function build_default_syntax(color_scheme: ColorScheme): Syntax { // then spread the default to each style for (const key of Object.keys({} as Syntax)) { syntax[key as keyof Syntax] = { - ...default_syntaxHighlightStyle, + ...default_syntax_highlight_style, } } diff --git a/styles/src/theme/theme_config.ts b/styles/src/theme/theme_config.ts index 176ae83bb769f4bdc38f83a64c5963084c56de11..26462bee6d43970c1db61e470fcab48aed20c1d0 100644 --- a/styles/src/theme/theme_config.ts +++ b/styles/src/theme/theme_config.ts @@ -17,15 +17,15 @@ interface ThemeMeta { * * Example: `MIT` */ - licenseType?: string | ThemeLicenseType - licenseUrl?: string - licenseFile: string - themeUrl?: string + license_type?: string | ThemeLicenseType + license_url?: string + license_file: string + theme_url?: string } export type ThemeFamilyMeta = Pick< ThemeMeta, - "name" | "author" | "licenseType" | "licenseUrl" + "name" | "author" | "license_type" | "license_url" > export interface ThemeConfigInputColors { @@ -62,7 +62,7 @@ interface ThemeConfigOverrides { } type ThemeConfigProperties = ThemeMeta & { - inputColor: ThemeConfigInputColors + input_color: ThemeConfigInputColors override: ThemeConfigOverrides } diff --git a/styles/src/theme/tokens/color_scheme.ts b/styles/src/theme/tokens/color_scheme.ts index 84e456a6e7f223d728fe6cee3e73b0bfa3b155bc..21334fb199a19f3e1dfae2b514b80679418bca54 100644 --- a/styles/src/theme/tokens/color_scheme.ts +++ b/styles/src/theme/tokens/color_scheme.ts @@ -23,30 +23,30 @@ interface ColorSchemeTokens { middle: LayerToken highest: LayerToken players: PlayersToken - popoverShadow: SingleBoxShadowToken - modalShadow: SingleBoxShadowToken + popover_shadow: SingleBoxShadowToken + modal_shadow: SingleBoxShadowToken syntax?: Partial } -const createShadowToken = ( +const create_shadow_token = ( shadow: Shadow, - tokenName: string + token_name: string ): SingleBoxShadowToken => { return { - name: tokenName, + name: token_name, type: TokenTypes.BOX_SHADOW, value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`, } } -const popoverShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { - const shadow = colorScheme.popoverShadow - return createShadowToken(shadow, "popoverShadow") +const popover_shadow_token = (theme: ColorScheme): SingleBoxShadowToken => { + const shadow = theme.popover_shadow + return create_shadow_token(shadow, "popover_shadow") } -const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { - const shadow = colorScheme.modalShadow - return createShadowToken(shadow, "modalShadow") +const modal_shadow_token = (theme: ColorScheme): SingleBoxShadowToken => { + const shadow = theme.modal_shadow + return create_shadow_token(shadow, "modal_shadow") } type ThemeSyntaxColorTokens = Record @@ -68,32 +68,32 @@ function syntaxHighlightStyleColorTokens( }, {} as ThemeSyntaxColorTokens) } -const syntaxTokens = ( - colorScheme: ColorScheme +const syntax_Tokens = ( + theme: ColorScheme ): ColorSchemeTokens["syntax"] => { - const syntax = editor(colorScheme).syntax + const syntax = editor(theme).syntax return syntaxHighlightStyleColorTokens(syntax) } -export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { +export function theme_tokens(theme: ColorScheme): ColorSchemeTokens { return { name: { name: "themeName", - value: colorScheme.name, + value: theme.name, type: TokenTypes.OTHER, }, appearance: { name: "themeAppearance", - value: colorScheme.is_light ? "light" : "dark", + value: theme.is_light ? "light" : "dark", type: TokenTypes.OTHER, }, - lowest: layerToken(colorScheme.lowest, "lowest"), - middle: layerToken(colorScheme.middle, "middle"), - highest: layerToken(colorScheme.highest, "highest"), - popoverShadow: popoverShadowToken(colorScheme), - modalShadow: modalShadowToken(colorScheme), - players: playersToken(colorScheme), - syntax: syntaxTokens(colorScheme), + lowest: layerToken(theme.lowest, "lowest"), + middle: layerToken(theme.middle, "middle"), + highest: layerToken(theme.highest, "highest"), + popover_shadow: popover_shadow_token(theme), + modal_shadow: modal_shadow_token(theme), + players: playersToken(theme), + syntax: syntax_Tokens(theme), } } diff --git a/styles/src/themes/andromeda/andromeda.ts b/styles/src/themes/andromeda/andromeda.ts index 52c29bb2ec2071670c2c0b7f4f6dfd990d834381..18699d21cd9b8118ec18358e8a6cc515c268d002 100644 --- a/styles/src/themes/andromeda/andromeda.ts +++ b/styles/src/themes/andromeda/andromeda.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const dark: ThemeConfig = { name: "Andromeda", author: "EliverLara", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/EliverLara/Andromeda", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/EliverLara/Andromeda", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#1E2025", @@ -26,14 +26,14 @@ export const dark: ThemeConfig = { "#F7F7F8", ]) .domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]), - red: colorRamp(chroma("#F92672")), - orange: colorRamp(chroma("#F39C12")), - yellow: colorRamp(chroma("#FFE66D")), - green: colorRamp(chroma("#96E072")), - cyan: colorRamp(chroma("#00E8C6")), - blue: colorRamp(chroma("#0CA793")), - violet: colorRamp(chroma("#8A3FA6")), - magenta: colorRamp(chroma("#C74DED")), + red: color_ramp(chroma("#F92672")), + orange: color_ramp(chroma("#F39C12")), + yellow: color_ramp(chroma("#FFE66D")), + green: color_ramp(chroma("#96E072")), + cyan: color_ramp(chroma("#00E8C6")), + blue: color_ramp(chroma("#0CA793")), + violet: color_ramp(chroma("#8A3FA6")), + magenta: color_ramp(chroma("#C74DED")), }, override: { syntax: {} }, } diff --git a/styles/src/themes/atelier/atelier-cave-dark.ts b/styles/src/themes/atelier/atelier-cave-dark.ts index ebec67b4c276639f22427182bd21dc72e8acbcfb..faf957b6423c04f5997eef566c60e3a72e853af9 100644 --- a/styles/src/themes/atelier/atelier-cave-dark.ts +++ b/styles/src/themes/atelier/atelier-cave-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Cave Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-cave-light.ts b/styles/src/themes/atelier/atelier-cave-light.ts index c1b7a05d4718171a81e4f8c45e0a70ee62e73625..856cd300436de7baa50e81de6e57f03a93a00e11 100644 --- a/styles/src/themes/atelier/atelier-cave-light.ts +++ b/styles/src/themes/atelier/atelier-cave-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Cave Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-dune-dark.ts b/styles/src/themes/atelier/atelier-dune-dark.ts index c2ebc424e77c6b30ad693c86e0c932f204f91a20..fb67fd2471a113dfed33847836276cb7cbac7044 100644 --- a/styles/src/themes/atelier/atelier-dune-dark.ts +++ b/styles/src/themes/atelier/atelier-dune-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Dune Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-dune-light.ts b/styles/src/themes/atelier/atelier-dune-light.ts index 01cb1d67cba048c2437c44c495a769eeab71d68e..5e9e5b69276562fb8ee5d945282c1ebe44b3e5fc 100644 --- a/styles/src/themes/atelier/atelier-dune-light.ts +++ b/styles/src/themes/atelier/atelier-dune-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Dune Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-estuary-dark.ts b/styles/src/themes/atelier/atelier-estuary-dark.ts index 8e32c1f68f5d78d115c7bf4aec999c442d882a67..0badf4371e5a629030765d76f60f1ee7d7078e0f 100644 --- a/styles/src/themes/atelier/atelier-estuary-dark.ts +++ b/styles/src/themes/atelier/atelier-estuary-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Estuary Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-estuary-light.ts b/styles/src/themes/atelier/atelier-estuary-light.ts index 75fcb8e8302d26ab747bb42b3c07cda630fa1c7f..adc77e760715b63c5955b36ab31db141c44e6e89 100644 --- a/styles/src/themes/atelier/atelier-estuary-light.ts +++ b/styles/src/themes/atelier/atelier-estuary-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Estuary Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-forest-dark.ts b/styles/src/themes/atelier/atelier-forest-dark.ts index 7ee7ae4ab13681a5413dc4cc97f3166c5dbed1a2..3e89518c0bb04095fabd86296853c31a66c0615e 100644 --- a/styles/src/themes/atelier/atelier-forest-dark.ts +++ b/styles/src/themes/atelier/atelier-forest-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Forest Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-forest-light.ts b/styles/src/themes/atelier/atelier-forest-light.ts index e12baf9904ef7a7e8c1fc6d2acdc67e6ea0be7e5..68d2c50876df452145dc0e5cd6a674370ecf37f5 100644 --- a/styles/src/themes/atelier/atelier-forest-light.ts +++ b/styles/src/themes/atelier/atelier-forest-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Forest Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-heath-dark.ts b/styles/src/themes/atelier/atelier-heath-dark.ts index 11751367a3f9d01d490d8ec9961e3f0cc092464d..c185d69e43737c420dd1a6809e2cd187bd3c7c60 100644 --- a/styles/src/themes/atelier/atelier-heath-dark.ts +++ b/styles/src/themes/atelier/atelier-heath-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Heath Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-heath-light.ts b/styles/src/themes/atelier/atelier-heath-light.ts index 07f4a9b3cb3194abda570408f1642355815a66f6..4414987e229a1d1dccff657c3f5d4de227cb51ba 100644 --- a/styles/src/themes/atelier/atelier-heath-light.ts +++ b/styles/src/themes/atelier/atelier-heath-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Heath Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-lakeside-dark.ts b/styles/src/themes/atelier/atelier-lakeside-dark.ts index b1c98ddfdf20aa45b614392436e4b764482b3288..7fdc3b4eba363d74614db2f49b0bf512388acacc 100644 --- a/styles/src/themes/atelier/atelier-lakeside-dark.ts +++ b/styles/src/themes/atelier/atelier-lakeside-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Lakeside Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-lakeside-light.ts b/styles/src/themes/atelier/atelier-lakeside-light.ts index d960444defa0ef3615c480d7e2d82e5573435089..bdda48f6c732f1b830c32be42002198d205a3699 100644 --- a/styles/src/themes/atelier/atelier-lakeside-light.ts +++ b/styles/src/themes/atelier/atelier-lakeside-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Lakeside Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-plateau-dark.ts b/styles/src/themes/atelier/atelier-plateau-dark.ts index 74693b24fd35e7f705ecbd71feb1360aa38e3133..ff287bc80dd6c033f65b086a571321915b2fb0cb 100644 --- a/styles/src/themes/atelier/atelier-plateau-dark.ts +++ b/styles/src/themes/atelier/atelier-plateau-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Plateau Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-plateau-light.ts b/styles/src/themes/atelier/atelier-plateau-light.ts index dd3130cea0d191966e0c6d4cb4d11fdfa41047b1..8a9fb989ad2105b8f6f594b713b6115904eae141 100644 --- a/styles/src/themes/atelier/atelier-plateau-light.ts +++ b/styles/src/themes/atelier/atelier-plateau-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Plateau Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-savanna-dark.ts b/styles/src/themes/atelier/atelier-savanna-dark.ts index c387ac5ae989ee75fcb5bd8eddfc7fd41840a53f..d94af30334ca378116ae79469e0181b9201c8744 100644 --- a/styles/src/themes/atelier/atelier-savanna-dark.ts +++ b/styles/src/themes/atelier/atelier-savanna-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Savanna Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-savanna-light.ts b/styles/src/themes/atelier/atelier-savanna-light.ts index 64edd406a8982abe6f6b76e043931a0ff4b20c3c..2426b05400a77472264e9e76333d5e4585313da6 100644 --- a/styles/src/themes/atelier/atelier-savanna-light.ts +++ b/styles/src/themes/atelier/atelier-savanna-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Savanna Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-seaside-dark.ts b/styles/src/themes/atelier/atelier-seaside-dark.ts index dbccb96013c8a06cadc05224d9857dbd3637bdd1..abb267f5a4078774ef4efbbd9e0ecf122bc074c3 100644 --- a/styles/src/themes/atelier/atelier-seaside-dark.ts +++ b/styles/src/themes/atelier/atelier-seaside-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Seaside Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-seaside-light.ts b/styles/src/themes/atelier/atelier-seaside-light.ts index a9c034ed4406c449baeac12a10644704b2d3dc4d..455e7795e145dcca3c84a719671253a55aef26be 100644 --- a/styles/src/themes/atelier/atelier-seaside-light.ts +++ b/styles/src/themes/atelier/atelier-seaside-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Seaside Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-sulphurpool-dark.ts b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts index edfc518b8e9d4972bdc5132f6e2e4811ad2166b4..3f33647daa9414b6619005b7d0e6931e718d17c8 100644 --- a/styles/src/themes/atelier/atelier-sulphurpool-dark.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Sulphurpool Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-sulphurpool-light.ts b/styles/src/themes/atelier/atelier-sulphurpool-light.ts index fbef6683bf99d83a9ba1c125c4a0790d7ce23981..2cb4d0453952135265adf349da176fe772c98811 100644 --- a/styles/src/themes/atelier/atelier-sulphurpool-light.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Sulphurpool Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/common.ts b/styles/src/themes/atelier/common.ts index 2e5be61f52d92d32a58edb6d82517cabd6a0957d..b76ccc5b607a2f149a35114a519ddaaf2d1b254d 100644 --- a/styles/src/themes/atelier/common.ts +++ b/styles/src/themes/atelier/common.ts @@ -24,12 +24,12 @@ export interface Variant { export const meta: ThemeFamilyMeta = { name: "Atelier", author: "Bram de Haan (http://atelierbramdehaan.nl)", - licenseType: ThemeLicenseType.MIT, - licenseUrl: + license_type: ThemeLicenseType.MIT, + license_url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/", } -export const buildSyntax = (variant: Variant): ThemeSyntax => { +export const build_syntax = (variant: Variant): ThemeSyntax => { const { colors } = variant return { primary: { color: colors.base06 }, diff --git a/styles/src/themes/ayu/ayu-dark.ts b/styles/src/themes/ayu/ayu-dark.ts index 7feddacd2b1543ecebba174709d3c8d59f12a278..a12ce08e29e1d905ade51c12803f324c0c5f7678 100644 --- a/styles/src/themes/ayu/ayu-dark.ts +++ b/styles/src/themes/ayu/ayu-dark.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.dark -const { ramps, syntax } = buildTheme(variant, false) +const { ramps, syntax } = build_theme(variant, false) export const theme: ThemeConfig = { name: `${meta.name} Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/ayu-light.ts b/styles/src/themes/ayu/ayu-light.ts index bf023852471bdcbfe109127ce679c60695fdccaa..aceda0d01751ec69253278c983531f2f3435c614 100644 --- a/styles/src/themes/ayu/ayu-light.ts +++ b/styles/src/themes/ayu/ayu-light.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.light -const { ramps, syntax } = buildTheme(variant, true) +const { ramps, syntax } = build_theme(variant, true) export const theme: ThemeConfig = { name: `${meta.name} Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/ayu-mirage.ts b/styles/src/themes/ayu/ayu-mirage.ts index d2a69e7ab6a52fb8664516e4d3ee85b36fe19551..9dd3ea7a6133e875faa7727cc54b11d45e94b6fd 100644 --- a/styles/src/themes/ayu/ayu-mirage.ts +++ b/styles/src/themes/ayu/ayu-mirage.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.mirage -const { ramps, syntax } = buildTheme(variant, false) +const { ramps, syntax } = build_theme(variant, false) export const theme: ThemeConfig = { name: `${meta.name} Mirage`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/common.ts b/styles/src/themes/ayu/common.ts index 1eb2c91916d3e374508cf4d0365046583c844e01..9caaee8e346d6a1706fba5d85f9140624394c1ed 100644 --- a/styles/src/themes/ayu/common.ts +++ b/styles/src/themes/ayu/common.ts @@ -1,7 +1,7 @@ import { dark, light, mirage } from "ayu" import { chroma, - colorRamp, + color_ramp, ThemeLicenseType, ThemeSyntax, ThemeFamilyMeta, @@ -13,7 +13,7 @@ export const ayu = { mirage, } -export const buildTheme = (t: typeof dark, light: boolean) => { +export const build_theme = (t: typeof dark, light: boolean) => { const color = { lightBlue: t.syntax.tag.hex(), yellow: t.syntax.func.hex(), @@ -48,20 +48,20 @@ export const buildTheme = (t: typeof dark, light: boolean) => { light ? t.editor.fg.hex() : t.editor.bg.hex(), light ? t.editor.bg.hex() : t.editor.fg.hex(), ]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma(color.lightBlue)), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma(color.lightBlue)), }, syntax, } } -export const buildSyntax = (t: typeof dark): ThemeSyntax => { +export const build_syntax = (t: typeof dark): ThemeSyntax => { return { constant: { color: t.syntax.constant.hex() }, "string.regex": { color: t.syntax.regexp.hex() }, @@ -80,6 +80,6 @@ export const buildSyntax = (t: typeof dark): ThemeSyntax => { export const meta: ThemeFamilyMeta = { name: "Ayu", author: "dempfi", - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/dempfi/ayu", + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/dempfi/ayu", } diff --git a/styles/src/themes/gruvbox/gruvbox-common.ts b/styles/src/themes/gruvbox/gruvbox-common.ts index 6a911ce731d781d5ea614618928d5bc131ffe38e..2fa6b58faadb91b5689c5eac93baf706f6faa391 100644 --- a/styles/src/themes/gruvbox/gruvbox-common.ts +++ b/styles/src/themes/gruvbox/gruvbox-common.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,8 +11,8 @@ import { const meta: ThemeFamilyMeta = { name: "Gruvbox", author: "morhetz ", - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/morhetz/gruvbox", + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/morhetz/gruvbox", } const color = { @@ -73,7 +73,7 @@ interface ThemeColors { gray: string } -const darkNeutrals = [ +const dark_neutrals = [ color.dark1, color.dark2, color.dark3, @@ -96,7 +96,7 @@ const dark: ThemeColors = { gray: color.light4, } -const lightNeutrals = [ +const light_neutrals = [ color.light1, color.light2, color.light3, @@ -119,14 +119,6 @@ const light: ThemeColors = { gray: color.dark4, } -const darkHardNeutral = [color.dark0_hard, ...darkNeutrals] -const darkNeutral = [color.dark0, ...darkNeutrals] -const darkSoftNeutral = [color.dark0_soft, ...darkNeutrals] - -const lightHardNeutral = [color.light0_hard, ...lightNeutrals] -const lightNeutral = [color.light0, ...lightNeutrals] -const lightSoftNeutral = [color.light0_soft, ...lightNeutrals] - interface Variant { name: string appearance: "light" | "dark" @@ -167,60 +159,68 @@ const variant: Variant[] = [ }, ] -const buildVariant = (variant: Variant): ThemeConfig => { +const dark_hard_neutral = [color.dark0_hard, ...dark_neutrals] +const dark_neutral = [color.dark0, ...dark_neutrals] +const dark_soft_neutral = [color.dark0_soft, ...dark_neutrals] + +const light_hard_neutral = [color.light0_hard, ...light_neutrals] +const light_neutral = [color.light0, ...light_neutrals] +const light_soft_neutral = [color.light0_soft, ...light_neutrals] + +const build_variant = (variant: Variant): ThemeConfig => { const { colors } = variant const name = `Gruvbox ${variant.name}` - const isLight = variant.appearance === "light" + const is_light = variant.appearance === "light" let neutral: string[] = [] switch (variant.name) { case "Dark Hard": - neutral = darkHardNeutral + neutral = dark_hard_neutral break case "Dark": - neutral = darkNeutral + neutral = dark_neutral break case "Dark Soft": - neutral = darkSoftNeutral + neutral = dark_soft_neutral break case "Light Hard": - neutral = lightHardNeutral + neutral = light_hard_neutral break case "Light": - neutral = lightNeutral + neutral = light_neutral break case "Light Soft": - neutral = lightSoftNeutral + neutral = light_soft_neutral break } const ramps = { - neutral: chroma.scale(isLight ? neutral.reverse() : neutral), - red: colorRamp(chroma(variant.colors.red)), - orange: colorRamp(chroma(variant.colors.orange)), - yellow: colorRamp(chroma(variant.colors.yellow)), - green: colorRamp(chroma(variant.colors.green)), - cyan: colorRamp(chroma(variant.colors.aqua)), - blue: colorRamp(chroma(variant.colors.blue)), - violet: colorRamp(chroma(variant.colors.purple)), - magenta: colorRamp(chroma(variant.colors.gray)), + neutral: chroma.scale(is_light ? neutral.reverse() : neutral), + red: color_ramp(chroma(variant.colors.red)), + orange: color_ramp(chroma(variant.colors.orange)), + yellow: color_ramp(chroma(variant.colors.yellow)), + green: color_ramp(chroma(variant.colors.green)), + cyan: color_ramp(chroma(variant.colors.aqua)), + blue: color_ramp(chroma(variant.colors.blue)), + violet: color_ramp(chroma(variant.colors.purple)), + magenta: color_ramp(chroma(variant.colors.gray)), } const syntax: ThemeSyntax = { - primary: { color: neutral[isLight ? 0 : 8] }, + primary: { color: neutral[is_light ? 0 : 8] }, "text.literal": { color: colors.blue }, comment: { color: colors.gray }, - punctuation: { color: neutral[isLight ? 1 : 7] }, - "punctuation.bracket": { color: neutral[isLight ? 3 : 5] }, - "punctuation.list_marker": { color: neutral[isLight ? 0 : 8] }, + punctuation: { color: neutral[is_light ? 1 : 7] }, + "punctuation.bracket": { color: neutral[is_light ? 3 : 5] }, + "punctuation.list_marker": { color: neutral[is_light ? 0 : 8] }, operator: { color: colors.aqua }, boolean: { color: colors.purple }, number: { color: colors.purple }, @@ -236,7 +236,7 @@ const buildVariant = (variant: Variant): ThemeConfig => { function: { color: colors.green }, "function.builtin": { color: colors.red }, variable: { color: colors.blue }, - property: { color: neutral[isLight ? 0 : 8] }, + property: { color: neutral[is_light ? 0 : 8] }, embedded: { color: colors.aqua }, link_text: { color: colors.aqua }, link_uri: { color: colors.purple }, @@ -247,18 +247,18 @@ const buildVariant = (variant: Variant): ThemeConfig => { name, author: meta.author, appearance: variant.appearance as ThemeAppearance, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } } // Variants -export const darkHard = buildVariant(variant[0]) -export const darkDefault = buildVariant(variant[1]) -export const darkSoft = buildVariant(variant[2]) -export const lightHard = buildVariant(variant[3]) -export const lightDefault = buildVariant(variant[4]) -export const lightSoft = buildVariant(variant[5]) +export const dark_hard = build_variant(variant[0]) +export const dark_default = build_variant(variant[1]) +export const dark_soft = build_variant(variant[2]) +export const light_hard = build_variant(variant[3]) +export const light_default = build_variant(variant[4]) +export const light_soft = build_variant(variant[5]) diff --git a/styles/src/themes/gruvbox/gruvbox-dark-hard.ts b/styles/src/themes/gruvbox/gruvbox-dark-hard.ts index 4102671189e856bbd0c508fc1d6be88ee55e8c0c..72757c99f2425cb0fab24c5be23d5b888bcd5816 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark-hard.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark-hard.ts @@ -1 +1 @@ -export { darkHard } from "./gruvbox-common" +export { dark_hard } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-dark-soft.ts b/styles/src/themes/gruvbox/gruvbox-dark-soft.ts index d550d63768d40aa97295c9a85922b3788cbf2f96..d8f63ed3311ea2eb6e7b50d9d6c9e2e5f84d788c 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark-soft.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark-soft.ts @@ -1 +1 @@ -export { darkSoft } from "./gruvbox-common" +export { dark_soft } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-dark.ts b/styles/src/themes/gruvbox/gruvbox-dark.ts index 05850028a40cb583d8a3beb7b706c52e0523e81c..0582baa0d8b8779c5e595efdf7d127a93c8113e8 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark.ts @@ -1 +1 @@ -export { darkDefault } from "./gruvbox-common" +export { dark_default } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light-hard.ts b/styles/src/themes/gruvbox/gruvbox-light-hard.ts index ec3cddec758379f6bf84b165d04eed31373e34a4..bcaea06a4116ceaae9f88436aa1ef811c2c12895 100644 --- a/styles/src/themes/gruvbox/gruvbox-light-hard.ts +++ b/styles/src/themes/gruvbox/gruvbox-light-hard.ts @@ -1 +1 @@ -export { lightHard } from "./gruvbox-common" +export { light_hard } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light-soft.ts b/styles/src/themes/gruvbox/gruvbox-light-soft.ts index 0888e847aca9ffdfb8a7147d058cd44f35e4562c..5eb79f647be57db20886bb22aff821d29a9dc4da 100644 --- a/styles/src/themes/gruvbox/gruvbox-light-soft.ts +++ b/styles/src/themes/gruvbox/gruvbox-light-soft.ts @@ -1 +1 @@ -export { lightSoft } from "./gruvbox-common" +export { light_soft } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light.ts b/styles/src/themes/gruvbox/gruvbox-light.ts index 6f53ce529965a65a7cb0901ea2703f10b4502922..dc54a33f2644469878773193bb0cb67ba37c5cc5 100644 --- a/styles/src/themes/gruvbox/gruvbox-light.ts +++ b/styles/src/themes/gruvbox/gruvbox-light.ts @@ -1 +1 @@ -export { lightDefault } from "./gruvbox-common" +export { light_default } from "./gruvbox-common" diff --git a/styles/src/themes/index.ts b/styles/src/themes/index.ts index 75853bc042747ac9138a6e3fb47c3ac5e98a2e6c..72bb100e7b823e6d37aea5f7d8d8c84fa8e5b362 100644 --- a/styles/src/themes/index.ts +++ b/styles/src/themes/index.ts @@ -1,82 +1,82 @@ import { ThemeConfig } from "../theme" -import { darkDefault as gruvboxDark } from "./gruvbox/gruvbox-dark" -import { darkHard as gruvboxDarkHard } from "./gruvbox/gruvbox-dark-hard" -import { darkSoft as gruvboxDarkSoft } from "./gruvbox/gruvbox-dark-soft" -import { lightDefault as gruvboxLight } from "./gruvbox/gruvbox-light" -import { lightHard as gruvboxLightHard } from "./gruvbox/gruvbox-light-hard" -import { lightSoft as gruvboxLightSoft } from "./gruvbox/gruvbox-light-soft" -import { dark as solarizedDark } from "./solarized/solarized" -import { light as solarizedLight } from "./solarized/solarized" -import { dark as andromedaDark } from "./andromeda/andromeda" -import { theme as oneDark } from "./one/one-dark" -import { theme as oneLight } from "./one/one-light" -import { theme as ayuLight } from "./ayu/ayu-light" -import { theme as ayuDark } from "./ayu/ayu-dark" -import { theme as ayuMirage } from "./ayu/ayu-mirage" -import { theme as rosePine } from "./rose-pine/rose-pine" -import { theme as rosePineDawn } from "./rose-pine/rose-pine-dawn" -import { theme as rosePineMoon } from "./rose-pine/rose-pine-moon" +import { dark_default as gruvbox_dark } from "./gruvbox/gruvbox-dark" +import { dark_hard as gruvbox_dark_hard } from "./gruvbox/gruvbox-dark-hard" +import { dark_soft as gruvbox_dark_soft } from "./gruvbox/gruvbox-dark-soft" +import { light_default as gruvbox_light } from "./gruvbox/gruvbox-light" +import { light_hard as gruvbox_light_hard } from "./gruvbox/gruvbox-light-hard" +import { light_soft as gruvbox_light_soft } from "./gruvbox/gruvbox-light-soft" +import { dark as solarized_dark } from "./solarized/solarized" +import { light as solarized_light } from "./solarized/solarized" +import { dark as andromeda_dark } from "./andromeda/andromeda" +import { theme as one_dark } from "./one/one-dark" +import { theme as one_light } from "./one/one-light" +import { theme as ayu_light } from "./ayu/ayu-light" +import { theme as ayu_dark } from "./ayu/ayu-dark" +import { theme as ayu_mirage } from "./ayu/ayu-mirage" +import { theme as rose_pine } from "./rose-pine/rose-pine" +import { theme as rose_pine_dawn } from "./rose-pine/rose-pine-dawn" +import { theme as rose_pine_moon } from "./rose-pine/rose-pine-moon" import { theme as sandcastle } from "./sandcastle/sandcastle" import { theme as summercamp } from "./summercamp/summercamp" -import { theme as atelierCaveDark } from "./atelier/atelier-cave-dark" -import { theme as atelierCaveLight } from "./atelier/atelier-cave-light" -import { theme as atelierDuneDark } from "./atelier/atelier-dune-dark" -import { theme as atelierDuneLight } from "./atelier/atelier-dune-light" -import { theme as atelierEstuaryDark } from "./atelier/atelier-estuary-dark" -import { theme as atelierEstuaryLight } from "./atelier/atelier-estuary-light" -import { theme as atelierForestDark } from "./atelier/atelier-forest-dark" -import { theme as atelierForestLight } from "./atelier/atelier-forest-light" -import { theme as atelierHeathDark } from "./atelier/atelier-heath-dark" -import { theme as atelierHeathLight } from "./atelier/atelier-heath-light" -import { theme as atelierLakesideDark } from "./atelier/atelier-lakeside-dark" -import { theme as atelierLakesideLight } from "./atelier/atelier-lakeside-light" -import { theme as atelierPlateauDark } from "./atelier/atelier-plateau-dark" -import { theme as atelierPlateauLight } from "./atelier/atelier-plateau-light" -import { theme as atelierSavannaDark } from "./atelier/atelier-savanna-dark" -import { theme as atelierSavannaLight } from "./atelier/atelier-savanna-light" -import { theme as atelierSeasideDark } from "./atelier/atelier-seaside-dark" -import { theme as atelierSeasideLight } from "./atelier/atelier-seaside-light" -import { theme as atelierSulphurpoolDark } from "./atelier/atelier-sulphurpool-dark" -import { theme as atelierSulphurpoolLight } from "./atelier/atelier-sulphurpool-light" +import { theme as atelier_cave_dark } from "./atelier/atelier-cave-dark" +import { theme as atelier_cave_light } from "./atelier/atelier-cave-light" +import { theme as atelier_dune_dark } from "./atelier/atelier-dune-dark" +import { theme as atelier_dune_light } from "./atelier/atelier-dune-light" +import { theme as atelier_estuary_dark } from "./atelier/atelier-estuary-dark" +import { theme as atelier_estuary_light } from "./atelier/atelier-estuary-light" +import { theme as atelier_forest_dark } from "./atelier/atelier-forest-dark" +import { theme as atelier_forest_light } from "./atelier/atelier-forest-light" +import { theme as atelier_heath_dark } from "./atelier/atelier-heath-dark" +import { theme as atelier_heath_light } from "./atelier/atelier-heath-light" +import { theme as atelier_lakeside_dark } from "./atelier/atelier-lakeside-dark" +import { theme as atelier_lakeside_light } from "./atelier/atelier-lakeside-light" +import { theme as atelier_plateau_dark } from "./atelier/atelier-plateau-dark" +import { theme as atelier_plateau_light } from "./atelier/atelier-plateau-light" +import { theme as atelier_savanna_dark } from "./atelier/atelier-savanna-dark" +import { theme as atelier_savanna_light } from "./atelier/atelier-savanna-light" +import { theme as atelier_seaside_dark } from "./atelier/atelier-seaside-dark" +import { theme as atelier_seaside_light } from "./atelier/atelier-seaside-light" +import { theme as atelier_sulphurpool_dark } from "./atelier/atelier-sulphurpool-dark" +import { theme as atelier_sulphurpool_light } from "./atelier/atelier-sulphurpool-light" export const themes: ThemeConfig[] = [ - oneDark, - oneLight, - ayuLight, - ayuDark, - ayuMirage, - gruvboxDark, - gruvboxDarkHard, - gruvboxDarkSoft, - gruvboxLight, - gruvboxLightHard, - gruvboxLightSoft, - rosePine, - rosePineDawn, - rosePineMoon, + one_dark, + one_light, + ayu_light, + ayu_dark, + ayu_mirage, + gruvbox_dark, + gruvbox_dark_hard, + gruvbox_dark_soft, + gruvbox_light, + gruvbox_light_hard, + gruvbox_light_soft, + rose_pine, + rose_pine_dawn, + rose_pine_moon, sandcastle, - solarizedDark, - solarizedLight, - andromedaDark, + solarized_dark, + solarized_light, + andromeda_dark, summercamp, - atelierCaveDark, - atelierCaveLight, - atelierDuneDark, - atelierDuneLight, - atelierEstuaryDark, - atelierEstuaryLight, - atelierForestDark, - atelierForestLight, - atelierHeathDark, - atelierHeathLight, - atelierLakesideDark, - atelierLakesideLight, - atelierPlateauDark, - atelierPlateauLight, - atelierSavannaDark, - atelierSavannaLight, - atelierSeasideDark, - atelierSeasideLight, - atelierSulphurpoolDark, - atelierSulphurpoolLight, + atelier_cave_dark, + atelier_cave_light, + atelier_dune_dark, + atelier_dune_light, + atelier_estuary_dark, + atelier_estuary_light, + atelier_forest_dark, + atelier_forest_light, + atelier_heath_dark, + atelier_heath_light, + atelier_lakeside_dark, + atelier_lakeside_light, + atelier_plateau_dark, + atelier_plateau_light, + atelier_savanna_dark, + atelier_savanna_light, + atelier_seaside_dark, + atelier_seaside_light, + atelier_sulphurpool_dark, + atelier_sulphurpool_light, ] diff --git a/styles/src/themes/one/one-dark.ts b/styles/src/themes/one/one-dark.ts index b8456603ce6e5c02c2a476e7861eb9a2fd43713b..1241668cc2a2434e3c5d6971e6eddf189a11a6f6 100644 --- a/styles/src/themes/one/one-dark.ts +++ b/styles/src/themes/one/one-dark.ts @@ -1,7 +1,7 @@ import { chroma, font_weights, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,7 +11,7 @@ const color = { white: "#ACB2BE", grey: "#5D636F", red: "#D07277", - darkRed: "#B1574B", + dark_red: "#B1574B", orange: "#C0966B", yellow: "#DFC184", green: "#A1C181", @@ -24,10 +24,10 @@ export const theme: ThemeConfig = { name: "One Dark", author: "simurai", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#282c34", @@ -40,14 +40,14 @@ export const theme: ThemeConfig = { "#c8ccd4", ]) .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma("#be5046")), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma("#be5046")), }, override: { syntax: { @@ -66,7 +66,7 @@ export const theme: ThemeConfig = { property: { color: color.red }, punctuation: { color: color.white }, "punctuation.list_marker": { color: color.red }, - "punctuation.special": { color: color.darkRed }, + "punctuation.special": { color: color.dark_red }, string: { color: color.green }, title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, diff --git a/styles/src/themes/one/one-light.ts b/styles/src/themes/one/one-light.ts index e14862f423b67ece038fbd09fd3f651761b0bad6..c3de7826c96679fb1c1f2b1a9d81324687d919b9 100644 --- a/styles/src/themes/one/one-light.ts +++ b/styles/src/themes/one/one-light.ts @@ -1,7 +1,7 @@ import { chroma, font_weights, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,7 +11,7 @@ const color = { black: "#383A41", grey: "#A2A3A7", red: "#D36050", - darkRed: "#B92C46", + dark_red: "#B92C46", orange: "#AD6F26", yellow: "#DFC184", green: "#659F58", @@ -25,11 +25,11 @@ export const theme: ThemeConfig = { name: "One Light", author: "simurai", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/atom/atom/tree/master/packages/one-light-ui", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#383A41", @@ -42,14 +42,14 @@ export const theme: ThemeConfig = { "#FAFAFA", ]) .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma(color.magenta)), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma(color.magenta)), }, override: { syntax: { @@ -67,7 +67,7 @@ export const theme: ThemeConfig = { property: { color: color.red }, punctuation: { color: color.black }, "punctuation.list_marker": { color: color.red }, - "punctuation.special": { color: color.darkRed }, + "punctuation.special": { color: color.dark_red }, string: { color: color.green }, title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, diff --git a/styles/src/themes/rose-pine/common.ts b/styles/src/themes/rose-pine/common.ts index 30906078ee1f8632099d8bd10f2f6aa5f0d639d2..5c5482a754a634bd2338af2613327554ec36d13c 100644 --- a/styles/src/themes/rose-pine/common.ts +++ b/styles/src/themes/rose-pine/common.ts @@ -14,9 +14,9 @@ export const color = { pine: "#31748f", foam: "#9ccfd8", iris: "#c4a7e7", - highlightLow: "#21202e", - highlightMed: "#403d52", - highlightHigh: "#524f67", + highlight_low: "#21202e", + highlight_med: "#403d52", + highlight_high: "#524f67", }, moon: { base: "#232136", @@ -31,9 +31,9 @@ export const color = { pine: "#3e8fb0", foam: "#9ccfd8", iris: "#c4a7e7", - highlightLow: "#2a283e", - highlightMed: "#44415a", - highlightHigh: "#56526e", + highlight_low: "#2a283e", + highlight_med: "#44415a", + highlight_high: "#56526e", }, dawn: { base: "#faf4ed", @@ -48,9 +48,9 @@ export const color = { pine: "#286983", foam: "#56949f", iris: "#907aa9", - highlightLow: "#f4ede8", - highlightMed: "#dfdad9", - highlightHigh: "#cecacd", + highlight_low: "#f4ede8", + highlight_med: "#dfdad9", + highlight_high: "#cecacd", }, } diff --git a/styles/src/themes/rose-pine/rose-pine-dawn.ts b/styles/src/themes/rose-pine/rose-pine-dawn.ts index 15d7b5de2d3084e43a62dc46dca32951ac027397..c78f1132dd34c2acc339a2f974a3d2087d2da7d6 100644 --- a/styles/src/themes/rose-pine/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine/rose-pine-dawn.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -17,16 +17,16 @@ export const theme: ThemeConfig = { name: "Rosé Pine Dawn", author: "edunfelt", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale( [ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, @@ -34,14 +34,14 @@ export const theme: ThemeConfig = { ].reverse() ) .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/rose-pine/rose-pine-moon.ts b/styles/src/themes/rose-pine/rose-pine-moon.ts index c5ef0c997f51605273007abc34d1fe4adf37a368..450d6865e7e8ddb193cb97efaae504ea157ade97 100644 --- a/styles/src/themes/rose-pine/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine/rose-pine-moon.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -17,29 +17,29 @@ export const theme: ThemeConfig = { name: "Rosé Pine Moon", author: "edunfelt", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, color.text, ]) .domain([0, 0.3, 0.55, 1]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/rose-pine/rose-pine.ts b/styles/src/themes/rose-pine/rose-pine.ts index 0f3b439338764d6e62df9d4a4a91b9ffc832532f..b305b5b5775fa3a58fb3fabdb8f7add00ab3de83 100644 --- a/styles/src/themes/rose-pine/rose-pine.ts +++ b/styles/src/themes/rose-pine/rose-pine.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -16,27 +16,27 @@ export const theme: ThemeConfig = { name: "Rosé Pine", author: "edunfelt", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, color.text, ]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/sandcastle/sandcastle.ts b/styles/src/themes/sandcastle/sandcastle.ts index 753828c66579e48d958066defb3a95aa1431d71c..b54c402e475aba8190b48425e1fac370dde2ce45 100644 --- a/styles/src/themes/sandcastle/sandcastle.ts +++ b/styles/src/themes/sandcastle/sandcastle.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const theme: ThemeConfig = { name: "Sandcastle", author: "gessig", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/gessig/base16-sandcastle-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/gessig/base16-sandcastle-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ "#282c34", "#2c323b", @@ -24,14 +24,14 @@ export const theme: ThemeConfig = { "#d5c4a1", "#fdf4c1", ]), - red: colorRamp(chroma("#B4637A")), - orange: colorRamp(chroma("#a07e3b")), - yellow: colorRamp(chroma("#a07e3b")), - green: colorRamp(chroma("#83a598")), - cyan: colorRamp(chroma("#83a598")), - blue: colorRamp(chroma("#528b8b")), - violet: colorRamp(chroma("#d75f5f")), - magenta: colorRamp(chroma("#a87322")), + red: color_ramp(chroma("#B4637A")), + orange: color_ramp(chroma("#a07e3b")), + yellow: color_ramp(chroma("#a07e3b")), + green: color_ramp(chroma("#83a598")), + cyan: color_ramp(chroma("#83a598")), + blue: color_ramp(chroma("#528b8b")), + violet: color_ramp(chroma("#d75f5f")), + magenta: color_ramp(chroma("#a87322")), }, override: { syntax: {} }, } diff --git a/styles/src/themes/solarized/solarized.ts b/styles/src/themes/solarized/solarized.ts index 4084757525cd131933bd6ae874f5cfc1415990fb..05e6f018ab7ac66da4e3eb114d26d82238001488 100644 --- a/styles/src/themes/solarized/solarized.ts +++ b/styles/src/themes/solarized/solarized.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -19,24 +19,24 @@ const ramps = { "#fdf6e3", ]) .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]), - red: colorRamp(chroma("#dc322f")), - orange: colorRamp(chroma("#cb4b16")), - yellow: colorRamp(chroma("#b58900")), - green: colorRamp(chroma("#859900")), - cyan: colorRamp(chroma("#2aa198")), - blue: colorRamp(chroma("#268bd2")), - violet: colorRamp(chroma("#6c71c4")), - magenta: colorRamp(chroma("#d33682")), + red: color_ramp(chroma("#dc322f")), + orange: color_ramp(chroma("#cb4b16")), + yellow: color_ramp(chroma("#b58900")), + green: color_ramp(chroma("#859900")), + cyan: color_ramp(chroma("#2aa198")), + blue: color_ramp(chroma("#268bd2")), + violet: color_ramp(chroma("#6c71c4")), + magenta: color_ramp(chroma("#d33682")), } export const dark: ThemeConfig = { name: "Solarized Dark", author: "Ethan Schoonover", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/altercation/solarized", - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/altercation/solarized", + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax: {} }, } @@ -44,9 +44,9 @@ export const light: ThemeConfig = { name: "Solarized Light", author: "Ethan Schoonover", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/altercation/solarized", - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/altercation/solarized", + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax: {} }, } diff --git a/styles/src/themes/summercamp/summercamp.ts b/styles/src/themes/summercamp/summercamp.ts index 08098d2e2fd009e8858d8967d6714800dd8d656a..f9037feae49aaee1007f2694ea8620139d414a59 100644 --- a/styles/src/themes/summercamp/summercamp.ts +++ b/styles/src/themes/summercamp/summercamp.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const theme: ThemeConfig = { name: "Summercamp", author: "zoefiri", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/zoefiri/base16-sc", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/zoefiri/base16-sc", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#1c1810", @@ -26,14 +26,14 @@ export const theme: ThemeConfig = { "#f8f5de", ]) .domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]), - red: colorRamp(chroma("#e35142")), - orange: colorRamp(chroma("#fba11b")), - yellow: colorRamp(chroma("#f2ff27")), - green: colorRamp(chroma("#5ceb5a")), - cyan: colorRamp(chroma("#5aebbc")), - blue: colorRamp(chroma("#489bf0")), - violet: colorRamp(chroma("#FF8080")), - magenta: colorRamp(chroma("#F69BE7")), + red: color_ramp(chroma("#e35142")), + orange: color_ramp(chroma("#fba11b")), + yellow: color_ramp(chroma("#f2ff27")), + green: color_ramp(chroma("#5ceb5a")), + cyan: color_ramp(chroma("#5aebbc")), + blue: color_ramp(chroma("#489bf0")), + violet: color_ramp(chroma("#FF8080")), + magenta: color_ramp(chroma("#F69BE7")), }, override: { syntax: {} }, } diff --git a/styles/src/utils/snake_case.ts b/styles/src/utils/snake_case.ts index cdd9684752826c5606b0e0273b5286bca57c85e0..38c8a90a9e864177dcea255fa5b87a87da5a9607 100644 --- a/styles/src/utils/snake_case.ts +++ b/styles/src/utils/snake_case.ts @@ -5,8 +5,8 @@ import { snakeCase } from "case-anything" // Typescript magic to convert any string from camelCase to snake_case at compile time type SnakeCase = S extends string ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S + ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + : S : S type SnakeCased = { From 0627c198fd26e3862ff6dc64f8ff86f7dc735689 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 10:57:19 -0400 Subject: [PATCH 033/169] WIP snake_case 4/? --- styles/src/component/icon_button.ts | 6 +- styles/src/component/text_button.ts | 6 +- styles/src/element/interactive.test.ts | 18 ++-- styles/src/style_tree/assistant.ts | 84 ++++++++-------- styles/src/style_tree/command_palette.ts | 14 ++- styles/src/style_tree/contact_finder.ts | 22 ++--- styles/src/style_tree/contact_list.ts | 44 ++++----- styles/src/style_tree/contact_notification.ts | 17 ++-- styles/src/style_tree/editor.ts | 96 +++++++++---------- styles/src/style_tree/feedback.ts | 28 +++--- styles/src/style_tree/hover_popover.ts | 45 +++++---- .../style_tree/incoming_call_notification.ts | 49 +++++----- styles/src/style_tree/picker.ts | 61 ++++++------ styles/src/style_tree/project_diagnostics.ts | 11 +-- styles/src/style_tree/project_panel.ts | 90 +++++++++-------- .../style_tree/project_shared_notification.ts | 48 +++++----- styles/src/style_tree/search.ts | 88 +++++++++-------- styles/src/style_tree/shared_screen.ts | 5 +- .../style_tree/simple_message_notification.ts | 37 ++++--- styles/src/style_tree/status_bar.ts | 78 +++++++-------- styles/src/style_tree/tab_bar.ts | 4 +- styles/src/style_tree/titlebar.ts | 4 +- styles/src/style_tree/welcome.ts | 4 +- styles/src/style_tree/workspace.ts | 14 +-- styles/src/theme/color.ts | 2 +- 25 files changed, 428 insertions(+), 447 deletions(-) diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 79891c2477809a1312c968241ee833b62f0922cd..4664928d555dce2a07b2e2d1afb1ed7f3f4fcc6b 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -11,9 +11,9 @@ export type Margin = { interface IconButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial } diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index 477c2515e320d5e1d4ac2b2cdaf7f8dd587cdde8..64a91de7b0424945e153563d36a012380796add1 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -10,9 +10,9 @@ import { Margin } from "./icon_button" interface TextButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial text_properties?: TextProperties diff --git a/styles/src/element/interactive.test.ts b/styles/src/element/interactive.test.ts index b0cc57875f4249e0700cc4f28c7e93a34cec49dc..0e0013fc071ae36e8c80513b56b9cb7d12764ee8 100644 --- a/styles/src/element/interactive.test.ts +++ b/styles/src/element/interactive.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect } from "vitest" describe("interactive", () => { it("creates an Interactive with base properties and states", () => { const result = interactive({ - base: { fontSize: 10, color: "#FFFFFF" }, + base: { font_size: 10, color: "#FFFFFF" }, state: { hovered: { color: "#EEEEEE" }, clicked: { color: "#CCCCCC" }, @@ -16,25 +16,25 @@ describe("interactive", () => { }) expect(result).toEqual({ - default: { color: "#FFFFFF", fontSize: 10 }, - hovered: { color: "#EEEEEE", fontSize: 10 }, - clicked: { color: "#CCCCCC", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, + hovered: { color: "#EEEEEE", font_size: 10 }, + clicked: { color: "#CCCCCC", font_size: 10 }, }) }) it("creates an Interactive with no base properties", () => { const result = interactive({ state: { - default: { color: "#FFFFFF", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, hovered: { color: "#EEEEEE" }, clicked: { color: "#CCCCCC" }, }, }) expect(result).toEqual({ - default: { color: "#FFFFFF", fontSize: 10 }, - hovered: { color: "#EEEEEE", fontSize: 10 }, - clicked: { color: "#CCCCCC", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, + hovered: { color: "#EEEEEE", font_size: 10 }, + clicked: { color: "#CCCCCC", font_size: 10 }, }) }) @@ -48,7 +48,7 @@ describe("interactive", () => { it("throws error when no other state besides default is present", () => { const state = { - default: { fontSize: 10 }, + default: { font_size: 10 }, } expect(() => interactive({ state })).toThrow(NOT_ENOUGH_STATES_ERROR) diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index fbbfbc4cf1cb5f0a487830247294b692dbe96434..1f14d65c8ece43db23b17fa6fec11b5987acb775 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -1,23 +1,21 @@ import { ColorScheme } from "../theme/color_scheme" import { text, border, background, foreground } from "./components" -import editor from "./editor" import { interactive } from "../element" -export default function assistant(colorScheme: ColorScheme): any { - const layer = colorScheme.highest +export default function assistant(theme: ColorScheme): any { return { container: { - background: editor(colorScheme).background, + background: background(theme.highest), padding: { left: 12 }, }, message_header: { margin: { bottom: 6, top: 6 }, - background: editor(colorScheme).background, + background: background(theme.highest), }, hamburger_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/hamburger_15.svg", dimensions: { width: 15, @@ -31,7 +29,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -39,7 +37,7 @@ export default function assistant(colorScheme: ColorScheme): any { split_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/split_message_15.svg", dimensions: { width: 15, @@ -53,7 +51,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -61,7 +59,7 @@ export default function assistant(colorScheme: ColorScheme): any { quote_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/quote_15.svg", dimensions: { width: 15, @@ -75,7 +73,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -83,7 +81,7 @@ export default function assistant(colorScheme: ColorScheme): any { assist_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/assist_15.svg", dimensions: { width: 15, @@ -97,7 +95,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -105,7 +103,7 @@ export default function assistant(colorScheme: ColorScheme): any { zoom_in_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/maximize_8.svg", dimensions: { width: 12, @@ -119,7 +117,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -127,7 +125,7 @@ export default function assistant(colorScheme: ColorScheme): any { zoom_out_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/minimize_8.svg", dimensions: { width: 12, @@ -141,7 +139,7 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, @@ -149,7 +147,7 @@ export default function assistant(colorScheme: ColorScheme): any { plus_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/plus_12.svg", dimensions: { width: 12, @@ -163,33 +161,33 @@ export default function assistant(colorScheme: ColorScheme): any { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), title: { - ...text(layer, "sans", "default", { size: "sm" }), + ...text(theme.highest, "sans", "default", { size: "sm" }), }, saved_conversation: { container: interactive({ base: { - background: background(layer, "on"), + background: background(theme.highest, "on"), padding: { top: 4, bottom: 4 }, }, state: { hovered: { - background: background(layer, "on", "hovered"), + background: background(theme.highest, "on", "hovered"), }, }, }), savedAt: { margin: { left: 8 }, - ...text(layer, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, title: { margin: { left: 16 }, - ...text(layer, "sans", "default", { + ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), @@ -197,7 +195,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, user_sender: { default: { - ...text(layer, "sans", "default", { + ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), @@ -205,7 +203,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, assistant_sender: { default: { - ...text(layer, "sans", "accent", { + ...text(theme.highest, "sans", "accent", { size: "sm", weight: "bold", }), @@ -213,7 +211,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, system_sender: { default: { - ...text(layer, "sans", "variant", { + ...text(theme.highest, "sans", "variant", { size: "sm", weight: "bold", }), @@ -221,51 +219,51 @@ export default function assistant(colorScheme: ColorScheme): any { }, sent_at: { margin: { top: 2, left: 8 }, - ...text(layer, "sans", "default", { size: "2xs" }), + ...text(theme.highest, "sans", "default", { size: "2xs" }), }, model: interactive({ base: { - background: background(layer, "on"), + background: background(theme.highest, "on"), margin: { left: 12, right: 12, top: 12 }, padding: 4, corner_radius: 4, - ...text(layer, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, state: { hovered: { - background: background(layer, "on", "hovered"), - border: border(layer, "on", { overlay: true }), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", { overlay: true }), }, }, }), remaining_tokens: { - background: background(layer, "on"), + background: background(theme.highest, "on"), margin: { top: 12, right: 24 }, padding: 4, corner_radius: 4, - ...text(layer, "sans", "positive", { size: "xs" }), + ...text(theme.highest, "sans", "positive", { size: "xs" }), }, no_remaining_tokens: { - background: background(layer, "on"), + background: background(theme.highest, "on"), margin: { top: 12, right: 24 }, padding: 4, corner_radius: 4, - ...text(layer, "sans", "negative", { size: "xs" }), + ...text(theme.highest, "sans", "negative", { size: "xs" }), }, error_icon: { margin: { left: 8 }, - color: foreground(layer, "negative"), + color: foreground(theme.highest, "negative"), width: 12, }, api_key_editor: { - background: background(layer, "on"), + background: background(theme.highest, "on"), corner_radius: 6, - text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { + text: text(theme.highest, "mono", "on"), + placeholder_text: text(theme.highest, "mono", "on", "disabled", { size: "xs", }), - selection: colorScheme.players[0], - border: border(layer, "on"), + selection: theme.players[0], + border: border(theme.highest, "on"), padding: { bottom: 4, left: 8, @@ -275,7 +273,7 @@ export default function assistant(colorScheme: ColorScheme): any { }, api_key_prompt: { padding: 10, - ...text(layer, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, } } diff --git a/styles/src/style_tree/command_palette.ts b/styles/src/style_tree/command_palette.ts index 9198f87299dcd7deab443556416296e667175683..ca9daad95b1d43b829d006a10f9e3899329adabc 100644 --- a/styles/src/style_tree/command_palette.ts +++ b/styles/src/style_tree/command_palette.ts @@ -1,16 +1,14 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { text, background } from "./components" import { toggleable } from "../element" -export default function command_palette(colorScheme: ColorScheme): any { - const layer = colorScheme.highest - +export default function command_palette(theme: ColorScheme): any { const key = toggleable({ base: { - text: text(layer, "mono", "variant", "default", { size: "xs" }), + text: text(theme.highest, "mono", "variant", "default", { size: "xs" }), corner_radius: 2, - background: background(layer, "on"), + background: background(theme.highest, "on"), padding: { top: 1, bottom: 1, @@ -25,8 +23,8 @@ export default function command_palette(colorScheme: ColorScheme): any { }, state: { active: { - text: text(layer, "mono", "on", "default", { size: "xs" }), - background: withOpacity(background(layer, "on"), 0.2), + text: text(theme.highest, "mono", "on", "default", { size: "xs" }), + background: with_opacity(background(theme.highest, "on"), 0.2), }, }, }) diff --git a/styles/src/style_tree/contact_finder.ts b/styles/src/style_tree/contact_finder.ts index f68ce06d3546112bf8979365ac5a72e6843aa5c1..9f02d450d93d7bc2e8bd9d86535ef82b602a7384 100644 --- a/styles/src/style_tree/contact_finder.ts +++ b/styles/src/style_tree/contact_finder.ts @@ -3,12 +3,10 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" export default function contact_finder(theme: ColorScheme): any { - const layer = theme.middle - const side_margin = 6 const contact_button = { - background: background(layer, "variant"), - color: foreground(layer, "variant"), + background: background(theme.middle, "variant"), + color: foreground(theme.middle, "variant"), icon_width: 8, button_width: 16, corner_radius: 8, @@ -16,12 +14,12 @@ export default function contact_finder(theme: ColorScheme): any { const picker_style = picker(theme) const picker_input = { - background: background(layer, "on"), + background: background(theme.middle, "on"), corner_radius: 6, - text: text(layer, "mono"), - placeholder_text: text(layer, "mono", "on", "disabled", { size: "xs" }), + text: text(theme.middle, "mono"), + placeholder_text: text(theme.middle, "mono", "on", "disabled", { size: "xs" }), selection: theme.players[0], - border: border(layer), + border: border(theme.middle), padding: { bottom: 4, left: 8, @@ -41,7 +39,7 @@ export default function contact_finder(theme: ColorScheme): any { ...picker_style.item, margin: { left: side_margin, right: side_margin }, }, - no_matches: picker_style.noMatches, + no_matches: picker_style.no_matches, input_editor: picker_input, empty_input_editor: picker_input, }, @@ -58,13 +56,13 @@ export default function contact_finder(theme: ColorScheme): any { contact_button: { ...contact_button, hover: { - background: background(layer, "variant", "hovered"), + background: background(theme.middle, "variant", "hovered"), }, }, disabled_contact_button: { ...contact_button, - background: background(layer, "disabled"), - color: foreground(layer, "disabled"), + background: background(theme.middle, "disabled"), + color: foreground(theme.middle, "disabled"), }, } } diff --git a/styles/src/style_tree/contact_list.ts b/styles/src/style_tree/contact_list.ts index b3b89b7e428ac51be854d263e142dcb6c2191986..93f88e2d4afe4df10a943b5d8e04b42d15160e69 100644 --- a/styles/src/style_tree/contact_list.ts +++ b/styles/src/style_tree/contact_list.ts @@ -8,19 +8,19 @@ import { } from "./components" import { interactive, toggleable } from "../element" export default function contacts_panel(theme: ColorScheme): any { - const nameMargin = 8 - const sidePadding = 12 + const name_margin = 8 + const side_padding = 12 const layer = theme.middle - const contactButton = { + const contact_button = { background: background(layer, "on"), color: foreground(layer, "on"), icon_width: 8, button_width: 16, corner_radius: 8, } - const projectRow = { + const project_row = { guest_avatar_spacing: 4, height: 24, guest_avatar: { @@ -30,19 +30,19 @@ export default function contacts_panel(theme: ColorScheme): any { name: { ...text(layer, "mono", { size: "sm" }), margin: { - left: nameMargin, + left: name_margin, right: 6, }, }, guests: { margin: { - left: nameMargin, - right: nameMargin, + left: name_margin, + right: name_margin, }, }, padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, } @@ -83,8 +83,8 @@ export default function contacts_panel(theme: ColorScheme): any { ...text(layer, "mono", { size: "sm" }), margin: { top: 14 }, padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, background: background(layer, "default"), // posiewic: breaking change }, @@ -140,8 +140,8 @@ export default function contacts_panel(theme: ColorScheme): any { inactive: { default: { padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, }, }, @@ -149,8 +149,8 @@ export default function contacts_panel(theme: ColorScheme): any { default: { background: background(layer, "active"), padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, }, }, @@ -174,12 +174,12 @@ export default function contacts_panel(theme: ColorScheme): any { contact_username: { ...text(layer, "mono", { size: "sm" }), margin: { - left: nameMargin, + left: name_margin, }, }, - contact_button_spacing: nameMargin, + contact_button_spacing: name_margin, contact_button: interactive({ - base: { ...contactButton }, + base: { ...contact_button }, state: { hovered: { background: background(layer, "hovered"), @@ -187,7 +187,7 @@ export default function contacts_panel(theme: ColorScheme): any { }, }), disabled_button: { - ...contactButton, + ...contact_button, background: background(layer, "on"), color: foreground(layer, "on"), }, @@ -217,15 +217,15 @@ export default function contacts_panel(theme: ColorScheme): any { project_row: toggleable({ base: interactive({ base: { - ...projectRow, + ...project_row, background: background(layer), icon: { - margin: { left: nameMargin }, + margin: { left: name_margin }, color: foreground(layer, "variant"), width: 12, }, name: { - ...projectRow.name, + ...project_row.name, ...text(layer, "mono", { size: "sm" }), }, }, diff --git a/styles/src/style_tree/contact_notification.ts b/styles/src/style_tree/contact_notification.ts index 71467f6584493a44bea86e68cd5880dc7bcd692e..8de5496d91e41d054e5f2eee29f08cc7e0c31054 100644 --- a/styles/src/style_tree/contact_notification.ts +++ b/styles/src/style_tree/contact_notification.ts @@ -1,24 +1,25 @@ import { ColorScheme } from "../theme/color_scheme" import { background, foreground, text } from "./components" import { interactive } from "../element" -const avatarSize = 12 -const headerPadding = 8 export default function contact_notification(theme: ColorScheme): any { + const avatar_size = 12 + const header_padding = 8 + return { header_avatar: { - height: avatarSize, - width: avatarSize, + height: avatar_size, + width: avatar_size, corner_radius: 6, }, header_message: { ...text(theme.lowest, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, + margin: { left: header_padding, right: header_padding }, }, header_height: 18, body_message: { ...text(theme.lowest, "sans", { size: "xs" }), - margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 }, + margin: { left: avatar_size + header_padding, top: 6, bottom: 6 }, }, button: interactive({ base: { @@ -40,9 +41,9 @@ export default function contact_notification(theme: ColorScheme): any { default: { color: foreground(theme.lowest, "variant"), icon_width: 8, - iconHeight: 8, + icon_height: 8, button_width: 8, - buttonHeight: 8, + button_height: 8, hover: { color: foreground(theme.lowest, "hovered"), }, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 95760ce1d0228c28e93cd7fc0c6993d75de1d8c9..67e67e0cf0a3c7cea7454df486acd1c159669ac5 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -1,4 +1,4 @@ -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { ColorScheme, Layer, StyleSets } from "../theme/color_scheme" import { background, @@ -27,7 +27,7 @@ export default function editor(theme: ColorScheme): any { }, } - function diagnostic(layer: Layer, styleSet: StyleSets) { + function diagnostic(layer: Layer, style_set: StyleSets) { return { text_scale_factor: 0.857, header: { @@ -36,8 +36,8 @@ export default function editor(theme: ColorScheme): any { }), }, message: { - text: text(layer, "sans", styleSet, "default", { size: "sm" }), - highlight_text: text(layer, "sans", styleSet, "default", { + text: text(layer, "sans", style_set, "default", { size: "sm" }), + highlight_text: text(layer, "sans", style_set, "default", { size: "sm", weight: "bold", }), @@ -50,7 +50,7 @@ export default function editor(theme: ColorScheme): any { return { text_color: syntax.primary.color, background: background(layer), - active_line_background: withOpacity(background(layer, "on"), 0.75), + active_line_background: with_opacity(background(layer, "on"), 0.75), highlighted_line_background: background(layer, "on"), // Inline autocomplete suggestions, Co-pilot suggestions, etc. suggestion: syntax.predictive, @@ -133,7 +133,7 @@ export default function editor(theme: ColorScheme): any { }, }, }, - foldBackground: foreground(layer, "variant"), + fold_background: foreground(layer, "variant"), }, diff: { deleted: is_light @@ -145,31 +145,31 @@ export default function editor(theme: ColorScheme): any { inserted: is_light ? theme.ramps.green(0.4).hex() : theme.ramps.green(0.5).hex(), - removedWidthEm: 0.275, - widthEm: 0.15, + removed_width_em: 0.275, + width_em: 0.15, corner_radius: 0.05, }, /** Highlights matching occurrences of what is under the cursor * as well as matched brackets */ - documentHighlightReadBackground: withOpacity( + document_highlight_read_background: with_opacity( foreground(layer, "accent"), 0.1 ), - documentHighlightWriteBackground: theme.ramps + document_highlight_write_background: theme.ramps .neutral(0.5) .alpha(0.4) .hex(), // TODO: This was blend * 2 - errorColor: background(layer, "negative"), - gutterBackground: background(layer), - gutterPaddingFactor: 3.5, - lineNumber: withOpacity(foreground(layer), 0.35), - lineNumberActive: foreground(layer), - renameFade: 0.6, - unnecessaryCodeFade: 0.5, + error_color: background(layer, "negative"), + gutter_background: background(layer), + gutter_padding_factor: 3.5, + line_number: with_opacity(foreground(layer), 0.35), + line_number_active: foreground(layer), + rename_fade: 0.6, + unnecessary_code_fade: 0.5, selection: theme.players[0], whitespace: theme.ramps.neutral(0.5).hex(), - guestSelections: [ + guest_selections: [ theme.players[1], theme.players[2], theme.players[3], @@ -187,20 +187,20 @@ export default function editor(theme: ColorScheme): any { }, border: border(theme.middle), shadow: theme.popover_shadow, - matchHighlight: foreground(theme.middle, "accent"), + match_highlight: foreground(theme.middle, "accent"), item: autocomplete_item, - hoveredItem: { + hovered_item: { ...autocomplete_item, - matchHighlight: foreground( + match_highlight: foreground( theme.middle, "accent", "hovered" ), background: background(theme.middle, "hovered"), }, - selectedItem: { + selected_item: { ...autocomplete_item, - matchHighlight: foreground( + match_highlight: foreground( theme.middle, "accent", "active" @@ -208,10 +208,10 @@ export default function editor(theme: ColorScheme): any { background: background(theme.middle, "active"), }, }, - diagnosticHeader: { + diagnostic_header: { background: background(theme.middle), - icon_widthFactor: 1.5, - textScaleFactor: 0.857, + icon_width_factor: 1.5, + text_scale_factor: 0.857, border: border(theme.middle, { bottom: true, top: true, @@ -229,16 +229,16 @@ export default function editor(theme: ColorScheme): any { }), }, message: { - highlightText: text(theme.middle, "sans", { + highlight_text: text(theme.middle, "sans", { size: "sm", weight: "bold", }), text: text(theme.middle, "sans", { size: "sm" }), }, }, - diagnosticPathHeader: { + diagnostic_path_header: { background: background(theme.middle), - textScaleFactor: 0.857, + text_scale_factor: 0.857, filename: text(theme.middle, "mono", { size: "sm" }), path: { ...text(theme.middle, "mono", { size: "sm" }), @@ -247,20 +247,20 @@ export default function editor(theme: ColorScheme): any { }, }, }, - errorDiagnostic: diagnostic(theme.middle, "negative"), - warningDiagnostic: diagnostic(theme.middle, "warning"), - informationDiagnostic: diagnostic(theme.middle, "accent"), - hintDiagnostic: diagnostic(theme.middle, "warning"), - invalidErrorDiagnostic: diagnostic(theme.middle, "base"), - invalidHintDiagnostic: diagnostic(theme.middle, "base"), - invalidInformationDiagnostic: diagnostic(theme.middle, "base"), - invalidWarningDiagnostic: diagnostic(theme.middle, "base"), + error_diagnostic: diagnostic(theme.middle, "negative"), + warning_diagnostic: diagnostic(theme.middle, "warning"), + information_diagnostic: diagnostic(theme.middle, "accent"), + hint_diagnostic: diagnostic(theme.middle, "warning"), + invalid_error_diagnostic: diagnostic(theme.middle, "base"), + invalid_hint_diagnostic: diagnostic(theme.middle, "base"), + invalid_information_diagnostic: diagnostic(theme.middle, "base"), + invalid_warning_diagnostic: diagnostic(theme.middle, "base"), hover_popover: hover_popover(theme), - linkDefinition: { + link_definition: { color: syntax.link_uri.color, underline: syntax.link_uri.underline, }, - jumpIcon: interactive({ + jump_icon: interactive({ base: { color: foreground(layer, "on"), icon_width: 20, @@ -282,12 +282,12 @@ export default function editor(theme: ColorScheme): any { scrollbar: { width: 12, - minHeightFactor: 1.0, + min_height_factor: 1.0, track: { border: border(layer, "variant", { left: true }), }, thumb: { - background: withOpacity(background(layer, "inverted"), 0.3), + background: with_opacity(background(layer, "inverted"), 0.3), border: { width: 1, color: border_color(layer, "variant"), @@ -299,17 +299,17 @@ export default function editor(theme: ColorScheme): any { }, git: { deleted: is_light - ? withOpacity(theme.ramps.red(0.5).hex(), 0.8) - : withOpacity(theme.ramps.red(0.4).hex(), 0.8), + ? with_opacity(theme.ramps.red(0.5).hex(), 0.8) + : with_opacity(theme.ramps.red(0.4).hex(), 0.8), modified: is_light - ? withOpacity(theme.ramps.yellow(0.5).hex(), 0.8) - : withOpacity(theme.ramps.yellow(0.4).hex(), 0.8), + ? with_opacity(theme.ramps.yellow(0.5).hex(), 0.8) + : with_opacity(theme.ramps.yellow(0.4).hex(), 0.8), inserted: is_light - ? withOpacity(theme.ramps.green(0.5).hex(), 0.8) - : withOpacity(theme.ramps.green(0.4).hex(), 0.8), + ? with_opacity(theme.ramps.green(0.5).hex(), 0.8) + : with_opacity(theme.ramps.green(0.4).hex(), 0.8), }, }, - compositionMark: { + composition_mark: { underline: { thickness: 1.0, color: border_color(layer), diff --git a/styles/src/style_tree/feedback.ts b/styles/src/style_tree/feedback.ts index 040c8994be10b05395c668adb09e63600122be22..ab3a40c148b54a1fd7a0a672df258707cd7f3aab 100644 --- a/styles/src/style_tree/feedback.ts +++ b/styles/src/style_tree/feedback.ts @@ -2,16 +2,14 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" import { interactive } from "../element" -export default function feedback(colorScheme: ColorScheme): any { - const layer = colorScheme.highest - +export default function feedback(theme: ColorScheme): any { return { submit_button: interactive({ base: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), + ...text(theme.highest, "mono", "on"), + background: background(theme.highest, "on"), corner_radius: 6, - border: border(layer, "on"), + border: border(theme.highest, "on"), margin: { right: 4, }, @@ -24,24 +22,24 @@ export default function feedback(colorScheme: ColorScheme): any { }, state: { clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), + ...text(theme.highest, "mono", "on", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: border(theme.highest, "on", "pressed"), }, hovered: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), + ...text(theme.highest, "mono", "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", "hovered"), }, }, }), button_margin: 8, - info_text_default: text(layer, "sans", "default", { size: "xs" }), - link_text_default: text(layer, "sans", "default", { + info_text_default: text(theme.highest, "sans", "default", { size: "xs" }), + link_text_default: text(theme.highest, "sans", "default", { size: "xs", underline: true, }), - link_text_hover: text(layer, "sans", "hovered", { + link_text_hover: text(theme.highest, "sans", "hovered", { size: "xs", underline: true, }), diff --git a/styles/src/style_tree/hover_popover.ts b/styles/src/style_tree/hover_popover.ts index 28f5b1740018e162ba0741e400e76cbc554b1fc8..e9a008b3c61426aaeb79beea37872cf56ed8388e 100644 --- a/styles/src/style_tree/hover_popover.ts +++ b/styles/src/style_tree/hover_popover.ts @@ -1,10 +1,9 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" -export default function hover_popover(colorScheme: ColorScheme): any { - const layer = colorScheme.middle - const baseContainer = { - background: background(layer), +export default function hover_popover(theme: ColorScheme): any { + const base_container = { + background: background(theme.middle), corner_radius: 8, padding: { left: 8, @@ -12,35 +11,35 @@ export default function hover_popover(colorScheme: ColorScheme): any { top: 4, bottom: 4, }, - shadow: colorScheme.popover_shadow, - border: border(layer), + shadow: theme.popover_shadow, + border: border(theme.middle), margin: { left: -8, }, } return { - container: baseContainer, - infoContainer: { - ...baseContainer, - background: background(layer, "accent"), - border: border(layer, "accent"), + container: base_container, + info_container: { + ...base_container, + background: background(theme.middle, "accent"), + border: border(theme.middle, "accent"), }, - warningContainer: { - ...baseContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), + warning_container: { + ...base_container, + background: background(theme.middle, "warning"), + border: border(theme.middle, "warning"), }, - errorContainer: { - ...baseContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), + error_container: { + ...base_container, + background: background(theme.middle, "negative"), + border: border(theme.middle, "negative"), }, - blockStyle: { + block_style: { padding: { top: 4 }, }, - prose: text(layer, "sans", { size: "sm" }), - diagnosticSourceHighlight: { color: foreground(layer, "accent") }, - highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better + prose: text(theme.middle, "sans", { size: "sm" }), + diagnostic_source_highlight: { color: foreground(theme.middle, "accent") }, + highlight: theme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better } } diff --git a/styles/src/style_tree/incoming_call_notification.ts b/styles/src/style_tree/incoming_call_notification.ts index 2c4fa21867e9cfe34da0044eaa5d1bdc855cf1f4..91947b9da5cfca5a7eefb1a1ac571fe6a24c4ea9 100644 --- a/styles/src/style_tree/incoming_call_notification.ts +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -2,49 +2,48 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" export default function incoming_call_notification( - colorScheme: ColorScheme + theme: ColorScheme ): unknown { - const layer = colorScheme.middle - const avatarSize = 48 + const avatar_size = 48 return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - callerContainer: { + window_height: 74, + window_width: 380, + background: background(theme.middle), + caller_container: { padding: 12, }, - callerAvatar: { - height: avatarSize, - width: avatarSize, - corner_radius: avatarSize / 2, + caller_avatar: { + height: avatar_size, + width: avatar_size, + corner_radius: avatar_size / 2, }, - callerMetadata: { + caller_metadata: { margin: { left: 10 }, }, - callerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), + caller_username: { + ...text(theme.middle, "sans", { size: "sm", weight: "bold" }), margin: { top: -3 }, }, - callerMessage: { - ...text(layer, "sans", "variant", { size: "xs" }), + caller_message: { + ...text(theme.middle, "sans", "variant", { size: "xs" }), margin: { top: -3 }, }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), + worktree_roots: { + ...text(theme.middle, "sans", "variant", { size: "xs", weight: "bold" }), margin: { top: -3 }, }, button_width: 96, - acceptButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true }), - ...text(layer, "sans", "positive", { + accept_button: { + background: background(theme.middle, "accent"), + border: border(theme.middle, { left: true, bottom: true }), + ...text(theme.middle, "sans", "positive", { size: "xs", weight: "bold", }), }, - declineButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "negative", { + decline_button: { + border: border(theme.middle, { left: true }), + ...text(theme.middle, "sans", "negative", { size: "xs", weight: "bold", }), diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index 22b526f1831c4486cc9251e4a827807b6cbe7d19..7ca76cd85f324d9cc640dd3845a1ca1d3e89a751 100644 --- a/styles/src/style_tree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -1,24 +1,23 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" -export default function picker(colorScheme: ColorScheme): any { - const layer = colorScheme.lowest +export default function picker(theme: ColorScheme): any { const container = { - background: background(layer), - border: border(layer), - shadow: colorScheme.modal_shadow, + background: background(theme.lowest), + border: border(theme.lowest), + shadow: theme.modal_shadow, corner_radius: 12, padding: { bottom: 4, }, } - const inputEditor = { - placeholderText: text(layer, "sans", "on", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "on"), - border: border(layer, { bottom: true }), + const input_editor = { + placeholder_text: text(theme.lowest, "sans", "on", "disabled"), + selection: theme.players[0], + text: text(theme.lowest, "mono", "on"), + border: border(theme.lowest, { bottom: true }), padding: { bottom: 8, left: 16, @@ -29,13 +28,13 @@ export default function picker(colorScheme: ColorScheme): any { bottom: 4, }, } - const emptyInputEditor: any = { ...inputEditor } - delete emptyInputEditor.border - delete emptyInputEditor.margin + const empty_input_editor: any = { ...input_editor } + delete empty_input_editor.border + delete empty_input_editor.margin return { ...container, - emptyContainer: { + empty_container: { ...container, padding: {}, }, @@ -54,21 +53,21 @@ export default function picker(colorScheme: ColorScheme): any { right: 4, }, corner_radius: 8, - text: text(layer, "sans", "variant"), - highlightText: text(layer, "sans", "accent", { + text: text(theme.lowest, "sans", "variant"), + highlight_text: text(theme.lowest, "sans", "accent", { weight: "bold", }), }, state: { hovered: { - background: withOpacity( - background(layer, "hovered"), + background: with_opacity( + background(theme.lowest, "hovered"), 0.5 ), }, clicked: { - background: withOpacity( - background(layer, "pressed"), + background: with_opacity( + background(theme.lowest, "pressed"), 0.5 ), }, @@ -77,20 +76,20 @@ export default function picker(colorScheme: ColorScheme): any { state: { active: { default: { - background: withOpacity( - background(layer, "base", "active"), + background: with_opacity( + background(theme.lowest, "base", "active"), 0.5 ), }, hovered: { - background: withOpacity( - background(layer, "hovered"), + background: with_opacity( + background(theme.lowest, "hovered"), 0.5 ), }, clicked: { - background: withOpacity( - background(layer, "pressed"), + background: with_opacity( + background(theme.lowest, "pressed"), 0.5 ), }, @@ -98,10 +97,10 @@ export default function picker(colorScheme: ColorScheme): any { }, }), - inputEditor, - emptyInputEditor, - noMatches: { - text: text(layer, "sans", "variant"), + input_editor, + empty_input_editor, + no_matches: { + text: text(theme.lowest, "sans", "variant"), padding: { bottom: 8, left: 16, diff --git a/styles/src/style_tree/project_diagnostics.ts b/styles/src/style_tree/project_diagnostics.ts index 10f556d12150332a79a506370096651bae217d1e..5962b98a26ce20ebe2c8c06245cb0d134c3397e9 100644 --- a/styles/src/style_tree/project_diagnostics.ts +++ b/styles/src/style_tree/project_diagnostics.ts @@ -1,13 +1,12 @@ import { ColorScheme } from "../theme/color_scheme" import { background, text } from "./components" -export default function project_diagnostics(colorScheme: ColorScheme): any { - const layer = colorScheme.highest +export default function project_diagnostics(theme: ColorScheme): any { return { - background: background(layer), - tabIconSpacing: 4, + background: background(theme.highest), + tab_icon_spacing: 4, tab_icon_width: 13, - tabSummarySpacing: 10, - emptyMessage: text(layer, "sans", "variant", { size: "md" }), + tab_summary_spacing: 10, + empty_message: text(theme.highest, "sans", "variant", { size: "md" }), } } diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index 589e120e381075fe2b12303a0c5a9b798212cf5a..346ffb7641e8c891cd567046f9716e94db9380fc 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -1,5 +1,5 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { Border, TextStyle, @@ -13,13 +13,11 @@ import merge from "ts-deepmerge" export default function project_panel(theme: ColorScheme): any { const { is_light } = theme - const layer = theme.middle - type EntryStateProps = { background?: string border?: Border text?: TextStyle - iconColor?: string + icon_color?: string } type EntryState = { @@ -45,17 +43,17 @@ export default function project_panel(theme: ColorScheme): any { const base_properties = { height: 22, - background: background(layer), - iconColor: foreground(layer, "variant"), - iconSize: 7, + background: background(theme.middle), + icon_color: foreground(theme.middle, "variant"), + icon_size: 7, icon_spacing: 5, - text: text(layer, "mono", "variant", { size: "sm" }), + text: text(theme.middle, "mono", "variant", { size: "sm" }), status: { ...git_status, }, } - const selectedStyle: EntryState | undefined = selected + const selected_style: EntryState | undefined = selected ? selected : unselected @@ -67,27 +65,27 @@ export default function project_panel(theme: ColorScheme): any { const unselected_hovered_style = merge( base_properties, unselected?.hovered ?? {}, - { background: background(layer, "variant", "hovered") } + { background: background(theme.middle, "variant", "hovered") } ) const unselected_clicked_style = merge( base_properties, unselected?.clicked ?? {}, - { background: background(layer, "variant", "pressed") } + { background: background(theme.middle, "variant", "pressed") } ) const selected_default_style = merge( base_properties, - selectedStyle?.default ?? {}, - { background: background(layer) } + selected_style?.default ?? {}, + { background: background(theme.middle) } ) const selected_hovered_style = merge( base_properties, - selectedStyle?.hovered ?? {}, - { background: background(layer, "variant", "hovered") } + selected_style?.hovered ?? {}, + { background: background(theme.middle, "variant", "hovered") } ) const selected_clicked_style = merge( base_properties, - selectedStyle?.clicked ?? {}, - { background: background(layer, "variant", "pressed") } + selected_style?.clicked ?? {}, + { background: background(theme.middle, "variant", "pressed") } ) return toggleable({ @@ -110,13 +108,13 @@ export default function project_panel(theme: ColorScheme): any { }) } - const defaultEntry = entry() + const default_entry = entry() return { - openProjectButton: interactive({ + open_project_button: interactive({ base: { - background: background(layer), - border: border(layer, "active"), + background: background(theme.middle), + border: border(theme.middle, "active"), corner_radius: 4, margin: { top: 16, @@ -129,59 +127,59 @@ export default function project_panel(theme: ColorScheme): any { left: 7, right: 7, }, - ...text(layer, "sans", "default", { size: "sm" }), + ...text(theme.middle, "sans", "default", { size: "sm" }), }, state: { hovered: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), }, clicked: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "pressed"), - border: border(layer, "active"), + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "pressed"), + border: border(theme.middle, "active"), }, }, }), - background: background(layer), + background: background(theme.middle), padding: { left: 6, right: 6, top: 0, bottom: 6 }, - indentWidth: 12, - entry: defaultEntry, - draggedEntry: { - ...defaultEntry.inactive.default, - text: text(layer, "mono", "on", { size: "sm" }), - background: withOpacity(background(layer, "on"), 0.9), - border: border(layer), + indent_width: 12, + entry: default_entry, + dragged_entry: { + ...default_entry.inactive.default, + text: text(theme.middle, "mono", "on", { size: "sm" }), + background: with_opacity(background(theme.middle, "on"), 0.9), + border: border(theme.middle), }, - ignoredEntry: entry( + ignored_entry: entry( { default: { - text: text(layer, "mono", "disabled"), + text: text(theme.middle, "mono", "disabled"), }, }, { default: { - iconColor: foreground(layer, "variant"), + icon_color: foreground(theme.middle, "variant"), }, } ), - cutEntry: entry( + cut_entry: entry( { default: { - text: text(layer, "mono", "disabled"), + text: text(theme.middle, "mono", "disabled"), }, }, { default: { - background: background(layer, "active"), - text: text(layer, "mono", "disabled", { size: "sm" }), + background: background(theme.middle, "active"), + text: text(theme.middle, "mono", "disabled", { size: "sm" }), }, } ), - filenameEditor: { - background: background(layer, "on"), - text: text(layer, "mono", "on", { size: "sm" }), + filename_editor: { + background: background(theme.middle, "on"), + text: text(theme.middle, "mono", "on", { size: "sm" }), selection: theme.players[0], }, } diff --git a/styles/src/style_tree/project_shared_notification.ts b/styles/src/style_tree/project_shared_notification.ts index 6fe8170a3ce3815c8eb6118c1309a671cfe0f2c4..d93c1d65d50f1f8cc85ca1e3d051b875efa37941 100644 --- a/styles/src/style_tree/project_shared_notification.ts +++ b/styles/src/style_tree/project_shared_notification.ts @@ -2,50 +2,48 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" export default function project_shared_notification( - colorScheme: ColorScheme + theme: ColorScheme ): unknown { - const layer = colorScheme.middle - - const avatarSize = 48 + const avatar_size = 48 return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - ownerContainer: { + window_height: 74, + window_width: 380, + background: background(theme.middle), + owner_container: { padding: 12, }, - ownerAvatar: { - height: avatarSize, - width: avatarSize, - corner_radius: avatarSize / 2, + owner_avatar: { + height: avatar_size, + width: avatar_size, + corner_radius: avatar_size / 2, }, - ownerMetadata: { + owner_metadata: { margin: { left: 10 }, }, - ownerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), + owner_username: { + ...text(theme.middle, "sans", { size: "sm", weight: "bold" }), margin: { top: -3 }, }, message: { - ...text(layer, "sans", "variant", { size: "xs" }), + ...text(theme.middle, "sans", "variant", { size: "xs" }), margin: { top: -3 }, }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), + worktree_roots: { + ...text(theme.middle, "sans", "variant", { size: "xs", weight: "bold" }), margin: { top: -3 }, }, button_width: 96, - openButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true }), - ...text(layer, "sans", "accent", { + open_button: { + background: background(theme.middle, "accent"), + border: border(theme.middle, { left: true, bottom: true }), + ...text(theme.middle, "sans", "accent", { size: "xs", weight: "bold", }), }, - dismissButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "variant", { + dismiss_button: { + border: border(theme.middle, { left: true }), + ...text(theme.middle, "sans", "variant", { size: "xs", weight: "bold", }), diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index 37040613b303bee0a032756e7359f24a32ae1170..da515114e7e33f4e4bd4e110176ed1918ccc4068 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -1,21 +1,19 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function search(colorScheme: ColorScheme): any { - const layer = colorScheme.highest - +export default function search(theme: ColorScheme): any { // Search input const editor = { - background: background(layer), + background: background(theme.highest), corner_radius: 8, - minWidth: 200, - maxWidth: 500, - placeholderText: text(layer, "mono", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "default"), - border: border(layer), + min_width: 200, + max_width: 500, + placeholder_text: text(theme.highest, "mono", "disabled"), + selection: theme.players[0], + text: text(theme.highest, "mono", "default"), + border: border(theme.highest), margin: { right: 12, }, @@ -27,22 +25,22 @@ export default function search(colorScheme: ColorScheme): any { }, } - const includeExcludeEditor = { + const include_exclude_editor = { ...editor, - minWidth: 100, - maxWidth: 250, + min_width: 100, + max_width: 250, } return { // TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive - matchBackground: withOpacity(foreground(layer, "accent"), 0.4), - optionButton: toggleable({ + match_background: with_opacity(foreground(theme.highest, "accent"), 0.4), + option_button: toggleable({ base: interactive({ base: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), + ...text(theme.highest, "mono", "on"), + background: background(theme.highest, "on"), corner_radius: 6, - border: border(layer, "on"), + border: border(theme.highest, "on"), margin: { right: 4, }, @@ -55,66 +53,66 @@ export default function search(colorScheme: ColorScheme): any { }, state: { hovered: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), + ...text(theme.highest, "mono", "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", "hovered"), }, clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), + ...text(theme.highest, "mono", "on", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: border(theme.highest, "on", "pressed"), }, }, }), state: { active: { default: { - ...text(layer, "mono", "accent"), + ...text(theme.highest, "mono", "accent"), }, hovered: { - ...text(layer, "mono", "accent", "hovered"), + ...text(theme.highest, "mono", "accent", "hovered"), }, clicked: { - ...text(layer, "mono", "accent", "pressed"), + ...text(theme.highest, "mono", "accent", "pressed"), }, }, }, }), editor, - invalidEditor: { + invalid_editor: { ...editor, - border: border(layer, "negative"), + border: border(theme.highest, "negative"), }, - includeExcludeEditor, - invalidIncludeExcludeEditor: { - ...includeExcludeEditor, - border: border(layer, "negative"), + include_exclude_editor, + invalid_include_exclude_editor: { + ...include_exclude_editor, + border: border(theme.highest, "negative"), }, - matchIndex: { - ...text(layer, "mono", "variant"), + match_index: { + ...text(theme.highest, "mono", "variant"), padding: { left: 6, }, }, - optionButtonGroup: { + option_button_group: { padding: { left: 12, right: 12, }, }, - includeExcludeInputs: { - ...text(layer, "mono", "variant"), + include_exclude_inputs: { + ...text(theme.highest, "mono", "variant"), padding: { right: 6, }, }, - resultsStatus: { - ...text(layer, "mono", "on"), + results_status: { + ...text(theme.highest, "mono", "on"), size: 18, }, - dismissButton: interactive({ + dismiss_button: interactive({ base: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), icon_width: 12, button_width: 14, padding: { @@ -124,10 +122,10 @@ export default function search(colorScheme: ColorScheme): any { }, state: { hovered: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, clicked: { - color: foreground(layer, "pressed"), + color: foreground(theme.highest, "pressed"), }, }, }), diff --git a/styles/src/style_tree/shared_screen.ts b/styles/src/style_tree/shared_screen.ts index bc4ac0b5d705cca2d2f25597ba26681bbfc6feaf..b57c483f1c27b106e44f6a482dc9d8c64cdacacb 100644 --- a/styles/src/style_tree/shared_screen.ts +++ b/styles/src/style_tree/shared_screen.ts @@ -1,9 +1,8 @@ import { ColorScheme } from "../theme/color_scheme" import { background } from "./components" -export default function sharedScreen(colorScheme: ColorScheme) { - const layer = colorScheme.highest +export default function sharedScreen(theme: ColorScheme) { return { - background: background(layer), + background: background(theme.highest), } } diff --git a/styles/src/style_tree/simple_message_notification.ts b/styles/src/style_tree/simple_message_notification.ts index 621bf21232a15bb665bdab91b0a5b738fd251730..896f90a51b7095b64596b8d9560bdd0d9eaf554d 100644 --- a/styles/src/style_tree/simple_message_notification.ts +++ b/styles/src/style_tree/simple_message_notification.ts @@ -2,21 +2,20 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive } from "../element" -const headerPadding = 8 - export default function simple_message_notification( - colorScheme: ColorScheme -): unknown { - const layer = colorScheme.middle + theme: ColorScheme +): any { + const header_padding = 8 + return { message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, right: header_padding }, }, - actionMessage: interactive({ + action_nessage: interactive({ base: { - ...text(layer, "sans", { size: "xs" }), - border: border(layer, "active"), + ...text(theme.middle, "sans", { size: "xs" }), + border: border(theme.middle, "active"), corner_radius: 4, padding: { top: 3, @@ -25,27 +24,27 @@ export default function simple_message_notification( right: 7, }, - margin: { left: headerPadding, top: 6, bottom: 6 }, + margin: { left: header_padding, top: 6, bottom: 6 }, }, state: { hovered: { - ...text(layer, "sans", "default", { size: "xs" }), - background: background(layer, "hovered"), - border: border(layer, "active"), + ...text(theme.middle, "sans", "default", { size: "xs" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), }, }, }), - dismissButton: interactive({ + dismiss_button: interactive({ base: { - color: foreground(layer), + color: foreground(theme.middle), icon_width: 8, - iconHeight: 8, + icon_height: 8, button_width: 8, - buttonHeight: 8, + button_height: 8, }, state: { hovered: { - color: foreground(layer, "hovered"), + color: foreground(theme.middle, "hovered"), }, }, }), diff --git a/styles/src/style_tree/status_bar.ts b/styles/src/style_tree/status_bar.ts index d67634d5a80436cd4e452619ef971032fb1bbf8c..652c8bf05c636f74ed03377cbed445263c26465d 100644 --- a/styles/src/style_tree/status_bar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -1,22 +1,22 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function status_bar(colorScheme: ColorScheme): any { - const layer = colorScheme.lowest +export default function status_bar(theme: ColorScheme): any { + const layer = theme.lowest - const statusContainer = { + const status_container = { corner_radius: 6, padding: { top: 3, bottom: 3, left: 6, right: 6 }, } - const diagnosticStatusContainer = { + const diagnostic_status_container = { corner_radius: 6, padding: { top: 1, bottom: 1, left: 6, right: 6 }, } return { height: 30, - itemSpacing: 8, + item_spacing: 8, padding: { top: 1, bottom: 1, @@ -24,8 +24,8 @@ export default function status_bar(colorScheme: ColorScheme): any { right: 6, }, border: border(layer, { top: true, overlay: true }), - cursorPosition: text(layer, "sans", "variant"), - activeLanguage: interactive({ + cursor_position: text(layer, "sans", "variant"), + active_language: interactive({ base: { padding: { left: 6, right: 6 }, ...text(layer, "sans", "variant"), @@ -36,83 +36,83 @@ export default function status_bar(colorScheme: ColorScheme): any { }, }, }), - autoUpdateProgressMessage: text(layer, "sans", "variant"), - autoUpdateDoneMessage: text(layer, "sans", "variant"), - lspStatus: interactive({ + auto_updat_progress_message: text(layer, "sans", "variant"), + auto_update_done_message: text(layer, "sans", "variant"), + lsp_status: interactive({ base: { - ...diagnosticStatusContainer, + ...diagnostic_status_container, icon_spacing: 4, icon_width: 14, height: 18, message: text(layer, "sans"), - iconColor: foreground(layer), + icon_color: foreground(layer), }, state: { hovered: { message: text(layer, "sans"), - iconColor: foreground(layer), + icon_color: foreground(layer), background: background(layer, "hovered"), }, }, }), - diagnosticMessage: interactive({ + diagnostic_message: interactive({ base: { ...text(layer, "sans"), }, state: { hovered: text(layer, "sans", "hovered") }, }), - diagnosticSummary: interactive({ + diagnostic_summary: interactive({ base: { height: 20, icon_width: 16, icon_spacing: 2, - summarySpacing: 6, + summary_spacing: 6, text: text(layer, "sans", { size: "sm" }), - iconColorOk: foreground(layer, "variant"), - iconColorWarning: foreground(layer, "warning"), - iconColorError: foreground(layer, "negative"), - containerOk: { + icon_color_ok: foreground(layer, "variant"), + icon_color_warning: foreground(layer, "warning"), + icon_color_error: foreground(layer, "negative"), + container_ok: { corner_radius: 6, padding: { top: 3, bottom: 3, left: 7, right: 7 }, }, - containerWarning: { - ...diagnosticStatusContainer, + container_warning: { + ...diagnostic_status_container, background: background(layer, "warning"), border: border(layer, "warning"), }, - containerError: { - ...diagnosticStatusContainer, + container_error: { + ...diagnostic_status_container, background: background(layer, "negative"), border: border(layer, "negative"), }, }, state: { hovered: { - iconColorOk: foreground(layer, "on"), - containerOk: { + icon_color_ok: foreground(layer, "on"), + container_ok: { background: background(layer, "on", "hovered"), }, - containerWarning: { + container_warning: { background: background(layer, "warning", "hovered"), border: border(layer, "warning", "hovered"), }, - containerError: { + container_error: { background: background(layer, "negative", "hovered"), border: border(layer, "negative", "hovered"), }, }, }, }), - panelButtons: { - groupLeft: {}, - groupBottom: {}, - groupRight: {}, + panel_buttons: { + group_left: {}, + group_bottom: {}, + group_right: {}, button: toggleable({ base: interactive({ base: { - ...statusContainer, - iconSize: 16, - iconColor: foreground(layer, "variant"), + ...status_container, + icon_size: 16, + icon_color: foreground(layer, "variant"), label: { margin: { left: 6 }, ...text(layer, "sans", { size: "sm" }), @@ -120,7 +120,7 @@ export default function status_bar(colorScheme: ColorScheme): any { }, state: { hovered: { - iconColor: foreground(layer, "hovered"), + icon_color: foreground(layer, "hovered"), background: background(layer, "variant"), }, }, @@ -128,15 +128,15 @@ export default function status_bar(colorScheme: ColorScheme): any { state: { active: { default: { - iconColor: foreground(layer, "active"), + icon_color: foreground(layer, "active"), background: background(layer, "active"), }, hovered: { - iconColor: foreground(layer, "hovered"), + icon_color: foreground(layer, "hovered"), background: background(layer, "hovered"), }, clicked: { - iconColor: foreground(layer, "pressed"), + icon_color: foreground(layer, "pressed"), background: background(layer, "pressed"), }, }, diff --git a/styles/src/style_tree/tab_bar.ts b/styles/src/style_tree/tab_bar.ts index 63f0b213a6c44307bcc2509401c3af60bc850541..dc869024bcf0b72ef57dba6e94d16ec77ef28858 100644 --- a/styles/src/style_tree/tab_bar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -1,5 +1,5 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { text, border, background, foreground } from "./components" import { interactive, toggleable } from "../element" @@ -71,7 +71,7 @@ export default function tab_bar(colorScheme: ColorScheme): any { const draggedTab = { ...activePaneActiveTab, - background: withOpacity(tab.background, 0.9), + background: with_opacity(tab.background, 0.9), border: undefined as any, shadow: colorScheme.popover_shadow, } diff --git a/styles/src/style_tree/titlebar.ts b/styles/src/style_tree/titlebar.ts index dc1d098a3c3369aeb061764f2c2f38e7f21038a1..5972ba4bdd90c6a49237f1c58d0add7ab95bc8fd 100644 --- a/styles/src/style_tree/titlebar.ts +++ b/styles/src/style_tree/titlebar.ts @@ -2,7 +2,7 @@ import { ColorScheme } from "../common" import { icon_button, toggleable_icon_button } from "../component/icon_button" import { toggleable_text_button } from "../component/text_button" import { interactive, toggleable } from "../element" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { background, border, foreground, text } from "./components" const ITEM_SPACING = 8 @@ -225,7 +225,7 @@ export function titlebar(theme: ColorScheme): any { // When the collaboration server is out of date, show a warning outdatedWarning: { ...text(theme.lowest, "sans", "warning", { size: "xs" }), - background: withOpacity(background(theme.lowest, "warning"), 0.3), + background: with_opacity(background(theme.lowest, "warning"), 0.3), border: border(theme.lowest, "warning"), margin: { left: ITEM_SPACING, diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts index 9ae9716f66b1d72cb9aab33c830b207ba53f352f..2e21e331707c098953c0447ccbe1cd23dc197f4b 100644 --- a/styles/src/style_tree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -1,5 +1,5 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { border, background, @@ -56,7 +56,7 @@ export default function welcome(colorScheme: ColorScheme): any { }, checkboxGroup: { border: border(layer, "variant"), - background: withOpacity(background(layer, "hovered"), 0.25), + background: with_opacity(background(layer, "hovered"), 0.25), corner_radius: 4, padding: { left: 12, diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index 8c60c9d402fe9000745b14f7b31bc0daf6246b25..02b6c80fa989a19ba0e694edc69e644bc70c5595 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -1,5 +1,5 @@ import { ColorScheme } from "../theme/color_scheme" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { background, border, @@ -25,14 +25,14 @@ export default function workspace(colorScheme: ColorScheme): any { height: 256, }, logo: svg( - withOpacity("#000000", colorScheme.is_light ? 0.6 : 0.8), + with_opacity("#000000", colorScheme.is_light ? 0.6 : 0.8), "icons/logo_96.svg", 256, 256 ), logoShadow: svg( - withOpacity( + with_opacity( colorScheme.is_light ? "#FFFFFF" : colorScheme.lowest.base.default.background, @@ -97,8 +97,8 @@ export default function workspace(colorScheme: ColorScheme): any { zoomedBackground: { cursor: "Arrow", background: is_light - ? withOpacity(background(colorScheme.lowest), 0.8) - : withOpacity(background(colorScheme.highest), 0.6), + ? with_opacity(background(colorScheme.lowest), 0.8) + : with_opacity(background(colorScheme.highest), 0.6), }, zoomedPaneForeground: { margin: 16, @@ -181,7 +181,7 @@ export default function workspace(colorScheme: ColorScheme): any { }), disconnectedOverlay: { ...text(layer, "sans"), - background: withOpacity(background(layer), 0.8), + background: with_opacity(background(layer), 0.8), }, notification: { margin: { top: 10 }, @@ -195,6 +195,6 @@ export default function workspace(colorScheme: ColorScheme): any { width: 400, margin: { right: 10, bottom: 10 }, }, - dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), + dropTargetOverlayColor: with_opacity(foreground(layer, "variant"), 0.5), } } diff --git a/styles/src/theme/color.ts b/styles/src/theme/color.ts index 58ee4ccc7c236f4312b3dd5658c0cb5acff7030c..83c2107483664986819a1f0e7c2eb42ac2c4b2d7 100644 --- a/styles/src/theme/color.ts +++ b/styles/src/theme/color.ts @@ -1,5 +1,5 @@ import chroma from "chroma-js" -export function withOpacity(color: string, opacity: number): string { +export function with_opacity(color: string, opacity: number): string { return chroma(color).alpha(opacity).hex() } From 97dc7b77f43a70a550f015068e12f4032e033ad0 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 11:31:23 -0400 Subject: [PATCH 034/169] WIP snake_case 5/? --- .../style_tree/simple_message_notification.ts | 2 +- styles/src/style_tree/status_bar.ts | 2 +- styles/src/style_tree/tab_bar.ts | 52 ++++---- styles/src/style_tree/titlebar.ts | 66 +++++----- .../src/style_tree/toolbar_dropdown_menu.ts | 42 +++---- styles/src/style_tree/tooltip.ts | 17 ++- styles/src/style_tree/update_notification.ts | 28 ++--- styles/src/style_tree/welcome.ts | 74 ++++++----- styles/src/style_tree/workspace.ts | 119 +++++++++--------- styles/src/theme/tokens/color_scheme.ts | 18 +-- styles/src/theme/tokens/layer.ts | 26 ++-- styles/src/theme/tokens/players.ts | 34 ++--- styles/src/theme/tokens/token.ts | 2 +- styles/src/themes/andromeda/LICENSE | 2 +- styles/src/themes/atelier/LICENSE | 2 +- styles/src/themes/ayu/LICENSE | 2 +- styles/src/themes/ayu/common.ts | 6 +- styles/src/themes/gruvbox/LICENSE | 2 +- styles/src/themes/one/LICENSE | 2 +- styles/src/themes/rose-pine/LICENSE | 2 +- styles/src/themes/sandcastle/LICENSE | 2 +- styles/src/themes/solarized/LICENSE | 2 +- styles/src/themes/summercamp/LICENSE | 2 +- 23 files changed, 250 insertions(+), 256 deletions(-) diff --git a/styles/src/style_tree/simple_message_notification.ts b/styles/src/style_tree/simple_message_notification.ts index 896f90a51b7095b64596b8d9560bdd0d9eaf554d..e9567e235a4bb7dbfbc56f8cb49e5df3e5391ee8 100644 --- a/styles/src/style_tree/simple_message_notification.ts +++ b/styles/src/style_tree/simple_message_notification.ts @@ -12,7 +12,7 @@ export default function simple_message_notification( ...text(theme.middle, "sans", { size: "xs" }), margin: { left: header_padding, right: header_padding }, }, - action_nessage: interactive({ + action_message: interactive({ base: { ...text(theme.middle, "sans", { size: "xs" }), border: border(theme.middle, "active"), diff --git a/styles/src/style_tree/status_bar.ts b/styles/src/style_tree/status_bar.ts index 652c8bf05c636f74ed03377cbed445263c26465d..bde40d570c5c2bc8b42af00a52eca9ccb7b8e26b 100644 --- a/styles/src/style_tree/status_bar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -36,7 +36,7 @@ export default function status_bar(theme: ColorScheme): any { }, }, }), - auto_updat_progress_message: text(layer, "sans", "variant"), + auto_update_progress_message: text(layer, "sans", "variant"), auto_update_done_message: text(layer, "sans", "variant"), lsp_status: interactive({ base: { diff --git a/styles/src/style_tree/tab_bar.ts b/styles/src/style_tree/tab_bar.ts index dc869024bcf0b72ef57dba6e94d16ec77ef28858..55fd2c314a9781d4719f5d16fd5cd498202f89a1 100644 --- a/styles/src/style_tree/tab_bar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -3,11 +3,11 @@ import { with_opacity } from "../theme/color" import { text, border, background, foreground } from "./components" import { interactive, toggleable } from "../element" -export default function tab_bar(colorScheme: ColorScheme): any { +export default function tab_bar(theme: ColorScheme): any { const height = 32 - const activeLayer = colorScheme.highest - const layer = colorScheme.middle + const active_layer = theme.highest + const layer = theme.middle const tab = { height, @@ -29,12 +29,12 @@ export default function tab_bar(colorScheme: ColorScheme): any { // Close icons close_icon_width: 8, - iconClose: foreground(layer, "variant"), - iconCloseActive: foreground(layer, "hovered"), + icon_close: foreground(layer, "variant"), + icon_close_active: foreground(layer, "hovered"), // Indicators - iconConflict: foreground(layer, "warning"), - iconDirty: foreground(layer, "accent"), + icon_conflict: foreground(layer, "warning"), + icon_dirty: foreground(layer, "accent"), // When two tabs of the same name are open, a label appears next to them description: { @@ -43,25 +43,25 @@ export default function tab_bar(colorScheme: ColorScheme): any { }, } - const activePaneActiveTab = { + const active_pane_active_tab = { ...tab, - background: background(activeLayer), - text: text(activeLayer, "sans", "active", { size: "sm" }), + background: background(active_layer), + text: text(active_layer, "sans", "active", { size: "sm" }), border: { ...tab.border, bottom: false, }, } - const inactivePaneInactiveTab = { + const inactive_pane_inactive_tab = { ...tab, background: background(layer), text: text(layer, "sans", "variant", { size: "sm" }), } - const inactivePaneActiveTab = { + const inactive_pane_active_tab = { ...tab, - background: background(activeLayer), + background: background(active_layer), text: text(layer, "sans", "variant", { size: "sm" }), border: { ...tab.border, @@ -69,31 +69,31 @@ export default function tab_bar(colorScheme: ColorScheme): any { }, } - const draggedTab = { - ...activePaneActiveTab, + const dragged_tab = { + ...active_pane_active_tab, background: with_opacity(tab.background, 0.9), border: undefined as any, - shadow: colorScheme.popover_shadow, + shadow: theme.popover_shadow, } return { height, background: background(layer), - activePane: { - activeTab: activePaneActiveTab, - inactiveTab: tab, + active_pane: { + active_tab: active_pane_active_tab, + inactive_tab: tab, }, - inactivePane: { - activeTab: inactivePaneActiveTab, - inactiveTab: inactivePaneInactiveTab, + inactive_pane: { + active_tab: inactive_pane_active_tab, + inactive_tab: inactive_pane_inactive_tab, }, - draggedTab, - paneButton: toggleable({ + dragged_tab, + pane_button: toggleable({ base: interactive({ base: { color: foreground(layer, "variant"), icon_width: 12, - button_width: activePaneActiveTab.height, + button_width: active_pane_active_tab.height, }, state: { hovered: { @@ -118,7 +118,7 @@ export default function tab_bar(colorScheme: ColorScheme): any { }, }, }), - paneButtonContainer: { + pane_button_container: { background: tab.background, border: { ...tab.border, diff --git a/styles/src/style_tree/titlebar.ts b/styles/src/style_tree/titlebar.ts index 5972ba4bdd90c6a49237f1c58d0add7ab95bc8fd..067d619bb524880c1ddd9085a889cf41d3177470 100644 --- a/styles/src/style_tree/titlebar.ts +++ b/styles/src/style_tree/titlebar.ts @@ -17,8 +17,8 @@ function build_spacing( group: spacing, item: spacing / 2, half_item: spacing / 4, - marginY: (container_height - element_height) / 2, - marginX: (container_height - element_height) / 2, + margin_y: (container_height - element_height) / 2, + margin_x: (container_height - element_height) / 2, } } @@ -26,15 +26,15 @@ function call_controls(theme: ColorScheme) { const button_height = 18 const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING) - const marginY = { - top: space.marginY, - bottom: space.marginY, + const margin_y = { + top: space.margin_y, + bottom: space.margin_y, } return { toggle_microphone_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.group, right: space.half_item, }, @@ -43,7 +43,7 @@ function call_controls(theme: ColorScheme) { toggle_speakers_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.half_item, right: space.half_item, }, @@ -51,7 +51,7 @@ function call_controls(theme: ColorScheme) { screen_share_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.half_item, right: space.group, }, @@ -150,20 +150,20 @@ function user_menu(theme: ColorScheme) { } } return { - userMenuButtonOnline: build_button({ online: true }), - userMenuButtonOffline: build_button({ online: false }), + user_menu_button_online: build_button({ online: true }), + user_menu_button_offline: build_button({ online: false }), } } export function titlebar(theme: ColorScheme): any { - const avatarWidth = 15 - const avatarOuterWidth = avatarWidth + 4 - const followerAvatarWidth = 14 - const followerAvatarOuterWidth = followerAvatarWidth + 4 + const avatar_width = 15 + const avatar_outer_width = avatar_width + 4 + const follower_avatar_width = 14 + const follower_avatar_outer_width = follower_avatar_width + 4 return { item_spacing: ITEM_SPACING, - facePileSpacing: 2, + face_pile_spacing: 2, height: TITLEBAR_HEIGHT, background: background(theme.lowest), border: border(theme.lowest, { bottom: true }), @@ -177,21 +177,21 @@ export function titlebar(theme: ColorScheme): any { highlight_color: text(theme.lowest, "sans", "active").color, // Collaborators - leaderAvatar: { - width: avatarWidth, - outerWidth: avatarOuterWidth, - corner_radius: avatarWidth / 2, - outerCornerRadius: avatarOuterWidth / 2, + leader_avatar: { + width: avatar_width, + outer_width: avatar_outer_width, + corner_radius: avatar_width / 2, + outer_corner_radius: avatar_outer_width / 2, }, - followerAvatar: { - width: followerAvatarWidth, - outerWidth: followerAvatarOuterWidth, - corner_radius: followerAvatarWidth / 2, - outerCornerRadius: followerAvatarOuterWidth / 2, + follower_avatar: { + width: follower_avatar_width, + outer_width: follower_avatar_outer_width, + corner_radius: follower_avatar_width / 2, + outer_corner_radius: follower_avatar_outer_width / 2, }, - inactiveAvatarGrayscale: true, - followerAvatarOverlap: 8, - leaderSelection: { + inactive_avatar_grayscale: true, + follower_avatar_overlap: 8, + leader_selection: { margin: { top: 4, bottom: 4, @@ -204,14 +204,14 @@ export function titlebar(theme: ColorScheme): any { }, corner_radius: 6, }, - avatarRibbon: { + avatar_ribbon: { height: 3, width: 14, // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. }, sign_in_button: toggleable_text_button(theme, {}), - offlineIcon: { + offline_icon: { color: foreground(theme.lowest, "variant"), width: 16, margin: { @@ -223,7 +223,7 @@ export function titlebar(theme: ColorScheme): any { }, // When the collaboration server is out of date, show a warning - outdatedWarning: { + outdated_warning: { ...text(theme.lowest, "sans", "warning", { size: "xs" }), background: with_opacity(background(theme.lowest, "warning"), 0.3), border: border(theme.lowest, "warning"), @@ -253,14 +253,14 @@ export function titlebar(theme: ColorScheme): any { }), // Jewel that notifies you that there are new contact requests - toggleContactsBadge: { + toggle_contacts_badge: { corner_radius: 3, padding: 2, margin: { top: 3, left: 3 }, border: border(theme.lowest), background: foreground(theme.lowest, "accent"), }, - shareButton: toggleable_text_button(theme, {}), + share_button: toggleable_text_button(theme, {}), user_menu: user_menu(theme), } } diff --git a/styles/src/style_tree/toolbar_dropdown_menu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts index 51e62db822edc8d0da82378ee25b5f3e03f17287..df5cc094c10619d06bf2c50af2c01def7ba29d69 100644 --- a/styles/src/style_tree/toolbar_dropdown_menu.ts +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -1,61 +1,59 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" -export default function dropdown_menu(colorScheme: ColorScheme): any { - const layer = colorScheme.middle - +export default function dropdown_menu(theme: ColorScheme): any { return { - rowHeight: 30, - background: background(layer), - border: border(layer), - shadow: colorScheme.popover_shadow, + row_height: 30, + background: background(theme.middle), + border: border(theme.middle), + shadow: theme.popover_shadow, header: interactive({ base: { - ...text(layer, "sans", { size: "sm" }), - secondaryText: text(layer, "sans", { + ...text(theme.middle, "sans", { size: "sm" }), + secondary_text: text(theme.middle, "sans", { size: "sm", color: "#aaaaaa", }), - secondaryTextSpacing: 10, + secondary_text_spacing: 10, padding: { left: 8, right: 8, top: 2, bottom: 2 }, corner_radius: 6, - background: background(layer, "on"), + background: background(theme.middle, "on"), }, state: { hovered: { - background: background(layer, "hovered"), + background: background(theme.middle, "hovered"), }, clicked: { - background: background(layer, "pressed"), + background: background(theme.middle, "pressed"), }, }, }), - sectionHeader: { - ...text(layer, "sans", { size: "sm" }), + section_header: { + ...text(theme.middle, "sans", { size: "sm" }), padding: { left: 8, right: 8, top: 8, bottom: 8 }, }, item: toggleable({ base: interactive({ base: { - ...text(layer, "sans", { size: "sm" }), - secondaryTextSpacing: 10, - secondaryText: text(layer, "sans", { size: "sm" }), + ...text(theme.middle, "sans", { size: "sm" }), + secondary_text_spacing: 10, + secondary_text: text(theme.middle, "sans", { size: "sm" }), padding: { left: 18, right: 18, top: 2, bottom: 2 }, }, state: { hovered: { - background: background(layer, "hovered"), - ...text(layer, "sans", "hovered", { size: "sm" }), + background: background(theme.middle, "hovered"), + ...text(theme.middle, "sans", "hovered", { size: "sm" }), }, }, }), state: { active: { default: { - background: background(layer, "active"), + background: background(theme.middle, "active"), }, hovered: { - background: background(layer, "hovered"), + background: background(theme.middle, "hovered"), }, }, }, diff --git a/styles/src/style_tree/tooltip.ts b/styles/src/style_tree/tooltip.ts index ea890232b5d45938cc0411e5cf087674deff5468..2fa5db04d4f96eee33cca9ea9190dff275ad7e0c 100644 --- a/styles/src/style_tree/tooltip.ts +++ b/styles/src/style_tree/tooltip.ts @@ -1,23 +1,22 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, text } from "./components" -export default function tooltip(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function tooltip(theme: ColorScheme): any { return { - background: background(layer), - border: border(layer), + background: background(theme.middle), + border: border(theme.middle), padding: { top: 4, bottom: 4, left: 8, right: 8 }, margin: { top: 6, left: 6 }, - shadow: colorScheme.popover_shadow, + shadow: theme.popover_shadow, corner_radius: 6, - text: text(layer, "sans", { size: "xs" }), + text: text(theme.middle, "sans", { size: "xs" }), keystroke: { - background: background(layer, "on"), + background: background(theme.middle, "on"), corner_radius: 4, margin: { left: 6 }, padding: { left: 4, right: 4 }, - ...text(layer, "mono", "on", { size: "xs", weight: "bold" }), + ...text(theme.middle, "mono", "on", { size: "xs", weight: "bold" }), }, - maxTextWidth: 200, + max_text_width: 200, } } diff --git a/styles/src/style_tree/update_notification.ts b/styles/src/style_tree/update_notification.ts index e3cf833ce85e0e83ac655b6285485af1c882be13..48581a5d210dc2948d415c6b3919284eba601823 100644 --- a/styles/src/style_tree/update_notification.ts +++ b/styles/src/style_tree/update_notification.ts @@ -2,37 +2,37 @@ import { ColorScheme } from "../theme/color_scheme" import { foreground, text } from "./components" import { interactive } from "../element" -const headerPadding = 8 -export default function update_notification(colorScheme: ColorScheme): any { - const layer = colorScheme.middle +export default function update_notification(theme: ColorScheme): any { + const header_padding = 8 + return { message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, right: header_padding }, }, - actionMessage: interactive({ + action_message: interactive({ base: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, top: 6, bottom: 6 }, + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, top: 6, bottom: 6 }, }, state: { hovered: { - color: foreground(layer, "hovered"), + color: foreground(theme.middle, "hovered"), }, }, }), - dismissButton: interactive({ + dismiss_button: interactive({ base: { - color: foreground(layer), + color: foreground(theme.middle), icon_width: 8, - iconHeight: 8, + icon_height: 8, button_width: 8, - buttonHeight: 8, + button_height: 8, }, state: { hovered: { - color: foreground(layer, "hovered"), + color: foreground(theme.middle, "hovered"), }, }, }), diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts index 2e21e331707c098953c0447ccbe1cd23dc197f4b..0d99ad3f77150077cb56d56629b8bb6fb6688124 100644 --- a/styles/src/style_tree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -10,10 +10,8 @@ import { } from "./components" import { interactive } from "../element" -export default function welcome(colorScheme: ColorScheme): any { - const layer = colorScheme.highest - - const checkboxBase = { +export default function welcome(theme: ColorScheme): any { + const checkbox_base = { corner_radius: 4, padding: { left: 3, @@ -21,8 +19,8 @@ export default function welcome(colorScheme: ColorScheme): any { top: 3, bottom: 3, }, - // shadow: colorScheme.popover_shadow, - border: border(layer), + // shadow: theme.popover_shadow, + border: border(theme.highest), margin: { right: 8, top: 5, @@ -33,30 +31,30 @@ export default function welcome(colorScheme: ColorScheme): any { const interactive_text_size: TextProperties = { size: "sm" } return { - pageWidth: 320, - logo: svg(foreground(layer, "default"), "icons/logo_96.svg", 64, 64), - logoSubheading: { - ...text(layer, "sans", "variant", { size: "md" }), + page_width: 320, + logo: svg(foreground(theme.highest, "default"), "icons/logo_96.svg", 64, 64), + logo_subheading: { + ...text(theme.highest, "sans", "variant", { size: "md" }), margin: { top: 10, bottom: 7, }, }, - buttonGroup: { + button_group: { margin: { top: 8, bottom: 16, }, }, - headingGroup: { + heading_group: { margin: { top: 8, bottom: 12, }, }, - checkboxGroup: { - border: border(layer, "variant"), - background: with_opacity(background(layer, "hovered"), 0.25), + checkbox_group: { + border: border(theme.highest, "variant"), + background: with_opacity(background(theme.highest, "hovered"), 0.25), corner_radius: 4, padding: { left: 12, @@ -66,8 +64,8 @@ export default function welcome(colorScheme: ColorScheme): any { }, button: interactive({ base: { - background: background(layer), - border: border(layer, "active"), + background: background(theme.highest), + border: border(theme.highest, "active"), corner_radius: 4, margin: { top: 4, @@ -79,23 +77,23 @@ export default function welcome(colorScheme: ColorScheme): any { left: 7, right: 7, }, - ...text(layer, "sans", "default", interactive_text_size), + ...text(theme.highest, "sans", "default", interactive_text_size), }, state: { hovered: { - ...text(layer, "sans", "default", interactive_text_size), - background: background(layer, "hovered"), + ...text(theme.highest, "sans", "default", interactive_text_size), + background: background(theme.highest, "hovered"), }, }, }), - usageNote: { - ...text(layer, "sans", "variant", { size: "2xs" }), + usage_note: { + ...text(theme.highest, "sans", "variant", { size: "2xs" }), padding: { top: -4, }, }, - checkboxContainer: { + checkbox_container: { margin: { top: 4, }, @@ -105,29 +103,29 @@ export default function welcome(colorScheme: ColorScheme): any { }, checkbox: { label: { - ...text(layer, "sans", interactive_text_size), + ...text(theme.highest, "sans", interactive_text_size), // Also supports margin, container, border, etc. }, - icon: svg(foreground(layer, "on"), "icons/check_12.svg", 12, 12), + icon: svg(foreground(theme.highest, "on"), "icons/check_12.svg", 12, 12), default: { - ...checkboxBase, - background: background(layer, "default"), - border: border(layer, "active"), + ...checkbox_base, + background: background(theme.highest, "default"), + border: border(theme.highest, "active"), }, checked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), }, hovered: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), }, - hoveredAndChecked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), + hovered_and_checked: { + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), }, }, } diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index 02b6c80fa989a19ba0e694edc69e644bc70c5595..82b05eab91f86c1c539c53588af1bf94e987360e 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -13,44 +13,43 @@ import tabBar from "./tab_bar" import { interactive } from "../element" import { titlebar } from "./titlebar" -export default function workspace(colorScheme: ColorScheme): any { - const layer = colorScheme.lowest - const is_light = colorScheme.is_light +export default function workspace(theme: ColorScheme): any { + const { is_light } = theme return { - background: background(colorScheme.lowest), - blankPane: { - logoContainer: { + background: background(theme.lowest), + blank_pane: { + logo_container: { width: 256, height: 256, }, logo: svg( - with_opacity("#000000", colorScheme.is_light ? 0.6 : 0.8), + with_opacity("#000000", theme.is_light ? 0.6 : 0.8), "icons/logo_96.svg", 256, 256 ), - logoShadow: svg( + logo_shadow: svg( with_opacity( - colorScheme.is_light + theme.is_light ? "#FFFFFF" - : colorScheme.lowest.base.default.background, - colorScheme.is_light ? 1 : 0.6 + : theme.lowest.base.default.background, + theme.is_light ? 1 : 0.6 ), "icons/logo_96.svg", 256, 256 ), - keyboardHints: { + keyboard_hints: { margin: { top: 96, }, corner_radius: 4, }, - keyboardHint: interactive({ + keyboard_hint: interactive({ base: { - ...text(layer, "sans", "variant", { size: "sm" }), + ...text(theme.lowest, "sans", "variant", { size: "sm" }), padding: { top: 3, left: 8, @@ -61,32 +60,32 @@ export default function workspace(colorScheme: ColorScheme): any { }, state: { hovered: { - ...text(layer, "sans", "active", { size: "sm" }), + ...text(theme.lowest, "sans", "active", { size: "sm" }), }, }, }), - keyboardHintWidth: 320, + keyboard_hint_width: 320, }, - joiningProjectAvatar: { + joining_project_avatar: { corner_radius: 40, width: 80, }, - joiningProjectMessage: { + joining_project_message: { padding: 12, - ...text(layer, "sans", { size: "lg" }), + ...text(theme.lowest, "sans", { size: "lg" }), }, - externalLocationMessage: { - background: background(colorScheme.middle, "accent"), - border: border(colorScheme.middle, "accent"), + external_location_message: { + background: background(theme.middle, "accent"), + border: border(theme.middle, "accent"), corner_radius: 6, padding: 12, margin: { bottom: 8, right: 8 }, - ...text(colorScheme.middle, "sans", "accent", { size: "xs" }), + ...text(theme.middle, "sans", "accent", { size: "xs" }), }, - leaderBorderOpacity: 0.7, - leaderBorderWidth: 2.0, - tabBar: tabBar(colorScheme), + leader_border_opacity: 0.7, + leader_border_width: 2.0, + tab_bar: tabBar(theme), modal: { margin: { bottom: 52, @@ -94,62 +93,62 @@ export default function workspace(colorScheme: ColorScheme): any { }, cursor: "Arrow", }, - zoomedBackground: { + zoomed_background: { cursor: "Arrow", background: is_light - ? with_opacity(background(colorScheme.lowest), 0.8) - : with_opacity(background(colorScheme.highest), 0.6), + ? with_opacity(background(theme.lowest), 0.8) + : with_opacity(background(theme.highest), 0.6), }, - zoomedPaneForeground: { + zoomed_pane_foreground: { margin: 16, - shadow: colorScheme.modal_shadow, - border: border(colorScheme.lowest, { overlay: true }), + shadow: theme.modal_shadow, + border: border(theme.lowest, { overlay: true }), }, - zoomedPanelForeground: { + zoomed_panel_foreground: { margin: 16, - border: border(colorScheme.lowest, { overlay: true }), + border: border(theme.lowest, { overlay: true }), }, dock: { left: { - border: border(layer, { right: true }), + border: border(theme.lowest, { right: true }), }, bottom: { - border: border(layer, { top: true }), + border: border(theme.lowest, { top: true }), }, right: { - border: border(layer, { left: true }), + border: border(theme.lowest, { left: true }), }, }, - paneDivider: { - color: border_color(layer), + pane_divider: { + color: border_color(theme.lowest), width: 1, }, - statusBar: statusBar(colorScheme), - titlebar: titlebar(colorScheme), + status_bar: statusBar(theme), + titlebar: titlebar(theme), toolbar: { height: 34, - background: background(colorScheme.highest), - border: border(colorScheme.highest, { bottom: true }), - itemSpacing: 8, - navButton: interactive({ + background: background(theme.highest), + border: border(theme.highest, { bottom: true }), + item_spacing: 8, + nav_button: interactive({ base: { - color: foreground(colorScheme.highest, "on"), + color: foreground(theme.highest, "on"), icon_width: 12, button_width: 24, corner_radius: 6, }, state: { hovered: { - color: foreground(colorScheme.highest, "on", "hovered"), + color: foreground(theme.highest, "on", "hovered"), background: background( - colorScheme.highest, + theme.highest, "on", "hovered" ), }, disabled: { color: foreground( - colorScheme.highest, + theme.highest, "on", "disabled" ), @@ -158,10 +157,10 @@ export default function workspace(colorScheme: ColorScheme): any { }), padding: { left: 8, right: 8, top: 4, bottom: 4 }, }, - breadcrumbHeight: 24, + breadcrumb_height: 24, breadcrumbs: interactive({ base: { - ...text(colorScheme.highest, "sans", "variant"), + ...text(theme.highest, "sans", "variant"), corner_radius: 6, padding: { left: 6, @@ -170,31 +169,31 @@ export default function workspace(colorScheme: ColorScheme): any { }, state: { hovered: { - color: foreground(colorScheme.highest, "on", "hovered"), + color: foreground(theme.highest, "on", "hovered"), background: background( - colorScheme.highest, + theme.highest, "on", "hovered" ), }, }, }), - disconnectedOverlay: { - ...text(layer, "sans"), - background: with_opacity(background(layer), 0.8), + disconnected_overlay: { + ...text(theme.lowest, "sans"), + background: with_opacity(background(theme.lowest), 0.8), }, notification: { margin: { top: 10 }, - background: background(colorScheme.middle), + background: background(theme.middle), corner_radius: 6, padding: 12, - border: border(colorScheme.middle), - shadow: colorScheme.popover_shadow, + border: border(theme.middle), + shadow: theme.popover_shadow, }, notifications: { width: 400, margin: { right: 10, bottom: 10 }, }, - dropTargetOverlayColor: with_opacity(foreground(layer, "variant"), 0.5), + drop_target_overlay_color: with_opacity(foreground(theme.lowest, "variant"), 0.5), } } diff --git a/styles/src/theme/tokens/color_scheme.ts b/styles/src/theme/tokens/color_scheme.ts index 21334fb199a19f3e1dfae2b514b80679418bca54..57793cf8dd052ad0228d4a72850c9e2ec2bfc7a4 100644 --- a/styles/src/theme/tokens/color_scheme.ts +++ b/styles/src/theme/tokens/color_scheme.ts @@ -51,29 +51,29 @@ const modal_shadow_token = (theme: ColorScheme): SingleBoxShadowToken => { type ThemeSyntaxColorTokens = Record -function syntaxHighlightStyleColorTokens( +function syntax_highlight_style_color_tokens( syntax: Syntax ): ThemeSyntaxColorTokens { - const styleKeys = Object.keys(syntax) as (keyof Syntax)[] + const style_keys = Object.keys(syntax) as (keyof Syntax)[] - return styleKeys.reduce((acc, styleKey) => { + return style_keys.reduce((acc, style_key) => { // Hack: The type of a style could be "Function" // This can happen because we have a "constructor" property on the syntax object // and a "constructor" property on the prototype of the syntax object // To work around this just assert that the type of the style is not a function - if (!syntax[styleKey] || typeof syntax[styleKey] === "function") + if (!syntax[style_key] || typeof syntax[style_key] === "function") return acc - const { color } = syntax[styleKey] as Required - return { ...acc, [styleKey]: colorToken(styleKey, color) } + const { color } = syntax[style_key] as Required + return { ...acc, [style_key]: colorToken(style_key, color) } }, {} as ThemeSyntaxColorTokens) } -const syntax_Tokens = ( +const syntax_tokens = ( theme: ColorScheme ): ColorSchemeTokens["syntax"] => { const syntax = editor(theme).syntax - return syntaxHighlightStyleColorTokens(syntax) + return syntax_highlight_style_color_tokens(syntax) } export function theme_tokens(theme: ColorScheme): ColorSchemeTokens { @@ -94,6 +94,6 @@ export function theme_tokens(theme: ColorScheme): ColorSchemeTokens { popover_shadow: popover_shadow_token(theme), modal_shadow: modal_shadow_token(theme), players: playersToken(theme), - syntax: syntax_Tokens(theme), + syntax: syntax_tokens(theme), } } diff --git a/styles/src/theme/tokens/layer.ts b/styles/src/theme/tokens/layer.ts index 10309e0d2e2bdda2f3c938ee20b1aa43d9aee4e0..a2e539092e4a9f0a4d49a74a615867e69bc6a923 100644 --- a/styles/src/theme/tokens/layer.ts +++ b/styles/src/theme/tokens/layer.ts @@ -1,6 +1,6 @@ import { SingleColorToken } from "@tokens-studio/types" import { Layer, Style, StyleSet } from "../color_scheme" -import { colorToken } from "./token" +import { color_token } from "./token" interface StyleToken { background: SingleColorToken @@ -27,36 +27,36 @@ export interface LayerToken { negative: StyleSetToken } -export const styleToken = (style: Style, name: string): StyleToken => { +export const style_token = (style: Style, name: string): StyleToken => { const token = { - background: colorToken(`${name}Background`, style.background), - border: colorToken(`${name}Border`, style.border), - foreground: colorToken(`${name}Foreground`, style.foreground), + background: color_token(`${name}Background`, style.background), + border: color_token(`${name}Border`, style.border), + foreground: color_token(`${name}Foreground`, style.foreground), } return token } -export const styleSetToken = ( - styleSet: StyleSet, +export const style_set_token = ( + style_set: StyleSet, name: string ): StyleSetToken => { const token: StyleSetToken = {} as StyleSetToken - for (const style in styleSet) { + for (const style in style_set) { const s = style as keyof StyleSet - token[s] = styleToken(styleSet[s], `${name}${style}`) + token[s] = style_token(style_set[s], `${name}${style}`) } return token } -export const layerToken = (layer: Layer, name: string): LayerToken => { +export const layer_token = (layer: Layer, name: string): LayerToken => { const token: LayerToken = {} as LayerToken - for (const styleSet in layer) { - const s = styleSet as keyof Layer - token[s] = styleSetToken(layer[s], `${name}${styleSet}`) + for (const style_set in layer) { + const s = style_set as keyof Layer + token[s] = style_set_token(layer[s], `${name}${style_set}`) } return token diff --git a/styles/src/theme/tokens/players.ts b/styles/src/theme/tokens/players.ts index 12f16343e9d08e670cb58ebe24c847a5c10786cf..68a8e676a71bb7e81d8134c75f77c05289ac4e62 100644 --- a/styles/src/theme/tokens/players.ts +++ b/styles/src/theme/tokens/players.ts @@ -1,36 +1,36 @@ import { SingleColorToken } from "@tokens-studio/types" -import { colorToken } from "./token" +import { color_token } from "./token" import { ColorScheme, Players } from "../color_scheme" export type PlayerToken = Record<"selection" | "cursor", SingleColorToken> export type PlayersToken = Record -function buildPlayerToken( - colorScheme: ColorScheme, +function build_player_token( + theme: ColorScheme, index: number ): PlayerToken { - const playerNumber = index.toString() as keyof Players + const player_number = index.toString() as keyof Players return { - selection: colorToken( + selection: color_token( `player${index}Selection`, - colorScheme.players[playerNumber].selection + theme.players[player_number].selection ), - cursor: colorToken( + cursor: color_token( `player${index}Cursor`, - colorScheme.players[playerNumber].cursor + theme.players[player_number].cursor ), } } -export const playersToken = (colorScheme: ColorScheme): PlayersToken => ({ - "0": buildPlayerToken(colorScheme, 0), - "1": buildPlayerToken(colorScheme, 1), - "2": buildPlayerToken(colorScheme, 2), - "3": buildPlayerToken(colorScheme, 3), - "4": buildPlayerToken(colorScheme, 4), - "5": buildPlayerToken(colorScheme, 5), - "6": buildPlayerToken(colorScheme, 6), - "7": buildPlayerToken(colorScheme, 7), +export const players_token = (theme: ColorScheme): PlayersToken => ({ + "0": build_player_token(theme, 0), + "1": build_player_token(theme, 1), + "2": build_player_token(theme, 2), + "3": build_player_token(theme, 3), + "4": build_player_token(theme, 4), + "5": build_player_token(theme, 5), + "6": build_player_token(theme, 6), + "7": build_player_token(theme, 7), }) diff --git a/styles/src/theme/tokens/token.ts b/styles/src/theme/tokens/token.ts index 2f1760778eaff686fdf26e03d05a0996e771ed2d..60e846ce94aafbbd98bf17948acfda983420f769 100644 --- a/styles/src/theme/tokens/token.ts +++ b/styles/src/theme/tokens/token.ts @@ -1,6 +1,6 @@ import { SingleColorToken, TokenTypes } from "@tokens-studio/types" -export function colorToken( +export function color_token( name: string, value: string, description?: string diff --git a/styles/src/themes/andromeda/LICENSE b/styles/src/themes/andromeda/LICENSE index bdd549491fac6822878157337aa5dc4d09ef53f2..9422adafa4dbd08333fb027481c2323c1d256872 100644 --- a/styles/src/themes/andromeda/LICENSE +++ b/styles/src/themes/andromeda/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/atelier/LICENSE b/styles/src/themes/atelier/LICENSE index 9f92967a0436d7118c20cf29bfb8844dba2699b1..47c46d04295dacb5373d352004286dcf1df8d3f9 100644 --- a/styles/src/themes/atelier/LICENSE +++ b/styles/src/themes/atelier/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/ayu/LICENSE b/styles/src/themes/ayu/LICENSE index 6b83ef0582f26b04f37f8b78ef5a3121b3f3a326..37a92292688fe679502eaa6be87ae5e2ce09eb03 100644 --- a/styles/src/themes/ayu/LICENSE +++ b/styles/src/themes/ayu/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/ayu/common.ts b/styles/src/themes/ayu/common.ts index 9caaee8e346d6a1706fba5d85f9140624394c1ed..2bd0bbf259aef2d9fc6c084f1da3c72379927026 100644 --- a/styles/src/themes/ayu/common.ts +++ b/styles/src/themes/ayu/common.ts @@ -15,14 +15,14 @@ export const ayu = { export const build_theme = (t: typeof dark, light: boolean) => { const color = { - lightBlue: t.syntax.tag.hex(), + light_blue: t.syntax.tag.hex(), yellow: t.syntax.func.hex(), blue: t.syntax.entity.hex(), green: t.syntax.string.hex(), teal: t.syntax.regexp.hex(), red: t.syntax.markup.hex(), orange: t.syntax.keyword.hex(), - lightYellow: t.syntax.special.hex(), + light_yellow: t.syntax.special.hex(), gray: t.syntax.comment.hex(), purple: t.syntax.constant.hex(), } @@ -55,7 +55,7 @@ export const build_theme = (t: typeof dark, light: boolean) => { cyan: color_ramp(chroma(color.teal)), blue: color_ramp(chroma(color.blue)), violet: color_ramp(chroma(color.purple)), - magenta: color_ramp(chroma(color.lightBlue)), + magenta: color_ramp(chroma(color.light_blue)), }, syntax, } diff --git a/styles/src/themes/gruvbox/LICENSE b/styles/src/themes/gruvbox/LICENSE index 2a9230614399a48916e74cfb74bd4625686c7bcb..0e18d6d7a93b8054677b18cbe8d0d1e914718452 100644 --- a/styles/src/themes/gruvbox/LICENSE +++ b/styles/src/themes/gruvbox/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/one/LICENSE b/styles/src/themes/one/LICENSE index dc07dc10ad0de56ebe0bfad8d65e82f0f5d627ef..f7637d33eafbc01c0ec8770eb9d900ddd8bca64b 100644 --- a/styles/src/themes/one/LICENSE +++ b/styles/src/themes/one/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/rose-pine/LICENSE b/styles/src/themes/rose-pine/LICENSE index dfd60136f95374fbe3e112a6051a4854b61ac4ec..12767334539540f92889031d8682d2487acc28bd 100644 --- a/styles/src/themes/rose-pine/LICENSE +++ b/styles/src/themes/rose-pine/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/sandcastle/LICENSE b/styles/src/themes/sandcastle/LICENSE index c66a06c51b46671cfe20194ac8ff545683c7a7e3..ba6559d8106fb5f9dfefc2cd0da8540c86ffdd68 100644 --- a/styles/src/themes/sandcastle/LICENSE +++ b/styles/src/themes/sandcastle/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/solarized/LICENSE b/styles/src/themes/solarized/LICENSE index 221eee6f152873e2e6c52ca4a89ac1d65118843b..2b5ddc4158b51df484a9df6e1724b9ef387860b6 100644 --- a/styles/src/themes/solarized/LICENSE +++ b/styles/src/themes/solarized/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/summercamp/LICENSE b/styles/src/themes/summercamp/LICENSE index d7525414ad01c246c21e908666064d6db4233901..dd49a64536aea1cecc98e709c923e1d7d69e23f6 100644 --- a/styles/src/themes/summercamp/LICENSE +++ b/styles/src/themes/summercamp/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. From a6f7e31bb9f5bb77d5be2dbbec87af20b085f4b1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 11:41:51 -0400 Subject: [PATCH 035/169] Update & format --- styles/src/build_licenses.ts | 4 +-- styles/src/build_themes.ts | 18 +++++----- styles/src/build_tokens.ts | 4 +-- styles/src/common.ts | 6 ++-- styles/src/component/icon_button.ts | 6 ++-- styles/src/component/text_button.ts | 6 ++-- styles/src/style_tree/assistant.ts | 2 +- styles/src/style_tree/command_palette.ts | 8 +++-- styles/src/style_tree/components.ts | 6 +--- styles/src/style_tree/contact_finder.ts | 4 ++- styles/src/style_tree/context_menu.ts | 4 ++- styles/src/style_tree/copilot.ts | 16 ++++++--- styles/src/style_tree/editor.ts | 12 ++----- styles/src/style_tree/feedback.ts | 4 ++- styles/src/style_tree/hover_popover.ts | 4 ++- .../style_tree/incoming_call_notification.ts | 5 ++- styles/src/style_tree/project_panel.ts | 4 ++- .../style_tree/project_shared_notification.ts | 5 ++- styles/src/style_tree/search.ts | 5 ++- .../style_tree/simple_message_notification.ts | 4 +-- .../src/style_tree/toolbar_dropdown_menu.ts | 4 ++- styles/src/style_tree/update_notification.ts | 1 - styles/src/style_tree/welcome.ts | 33 ++++++++++++++++--- styles/src/style_tree/workspace.ts | 23 ++++--------- styles/src/theme/syntax.ts | 5 ++- styles/src/theme/tokens/color_scheme.ts | 20 +++++------ styles/src/theme/tokens/players.ts | 5 +-- styles/src/themes/one/one-dark.ts | 3 +- styles/src/utils/snake_case.ts | 4 +-- 29 files changed, 127 insertions(+), 98 deletions(-) diff --git a/styles/src/build_licenses.ts b/styles/src/build_licenses.ts index 9cfaafdb750ce42207b2cfab5b2048c4ed72a50d..76c18dfee1fcb63e346d7bad8f093d57fae97dbe 100644 --- a/styles/src/build_licenses.ts +++ b/styles/src/build_licenses.ts @@ -20,7 +20,7 @@ function parse_accepted_toml(file: string): string[] { function check_licenses(themes: ThemeConfig[]) { for (const theme of themes) { - if (!theme.licenseFile) { + if (!theme.license_file) { throw Error(`Theme ${theme.name} should have a LICENSE file`) } } @@ -29,7 +29,7 @@ function check_licenses(themes: ThemeConfig[]) { function generate_license_file(themes: ThemeConfig[]) { check_licenses(themes) for (const theme of themes) { - const license_text = fs.readFileSync(theme.licenseFile).toString() + const license_text = fs.readFileSync(theme.license_file).toString() write_license(theme.name, license_text, theme.license_url) } } diff --git a/styles/src/build_themes.ts b/styles/src/build_themes.ts index 98ab8d27081dd0c0d7d58c758b343cb0f031c425..5a091719df2cf1bd115e0570d58fc84b18a66fc4 100644 --- a/styles/src/build_themes.ts +++ b/styles/src/build_themes.ts @@ -3,13 +3,11 @@ import { tmpdir } from "os" import * as path from "path" import app from "./style_tree/app" import { ColorScheme, create_color_scheme } from "./theme/color_scheme" -import snakeCase from "./utils/snake_case" import { themes } from "./themes" const assets_directory = `${__dirname}/../../assets` const temp_directory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) -// Clear existing themes function clear_themes(theme_directory: string) { if (!fs.existsSync(theme_directory)) { fs.mkdirSync(theme_directory, { recursive: true }) @@ -22,22 +20,24 @@ function clear_themes(theme_directory: string) { } } -function write_themes(color_schemes: ColorScheme[], output_directory: string) { +function write_themes(themes: ColorScheme[], output_directory: string) { clear_themes(output_directory) - for (const color_scheme of color_schemes) { - const style_tree = snakeCase(app(color_scheme)) + for (const color_scheme of themes) { + const style_tree = app(color_scheme) const style_tree_json = JSON.stringify(style_tree, null, 2) const temp_path = path.join(temp_directory, `${color_scheme.name}.json`) - const out_path = path.join(output_directory, `${color_scheme.name}.json`) + const out_path = path.join( + output_directory, + `${color_scheme.name}.json` + ) fs.writeFileSync(temp_path, style_tree_json) fs.renameSync(temp_path, out_path) console.log(`- ${out_path} created`) } } -const color_schemes: ColorScheme[] = themes.map((theme) => +const all_themes: ColorScheme[] = themes.map((theme) => create_color_scheme(theme) ) -// Write new themes to theme directory -write_themes(color_schemes, `${assets_directory}/themes`) +write_themes(all_themes, `${assets_directory}/themes`) diff --git a/styles/src/build_tokens.ts b/styles/src/build_tokens.ts index 09eed6a7b956e0f2e53865bbcf6bd470a529b72b..e33c3712e64d94d56b9b20d9317c270dd0b0aa28 100644 --- a/styles/src/build_tokens.ts +++ b/styles/src/build_tokens.ts @@ -3,7 +3,7 @@ import * as path from "path" import { ColorScheme, create_color_scheme } from "./common" import { themes } from "./themes" import { slugify } from "./utils/slugify" -import { colorSchemeTokens as color_scheme_tokens } from "./theme/tokens/color_scheme" +import { theme_tokens } from "./theme/tokens/color_scheme" const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens") const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json") @@ -60,7 +60,7 @@ function write_tokens(themes: ColorScheme[], tokens_directory: string) { for (const theme of themes) { const file_name = slugify(theme.name) + ".json" - const tokens = color_scheme_tokens(theme) + const tokens = theme_tokens(theme) const tokens_json = JSON.stringify(tokens, null, 2) const out_path = path.join(tokens_directory, file_name) fs.writeFileSync(out_path, tokens_json, { mode: 0o644 }) diff --git a/styles/src/common.ts b/styles/src/common.ts index 6c90de40942c6975d218b2843b62030086257008..054b2837914c89d82ffc584a1f5ba14e5a34f8ae 100644 --- a/styles/src/common.ts +++ b/styles/src/common.ts @@ -12,12 +12,10 @@ export const font_sizes = { xs: 12, sm: 14, md: 16, - lg: 18 + lg: 18, } -export type FontWeight = - | "normal" - | "bold" +export type FontWeight = "normal" | "bold" export const font_weights: { [key: string]: FontWeight } = { normal: "normal", diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 4664928d555dce2a07b2e2d1afb1ed7f3f4fcc6b..79891c2477809a1312c968241ee833b62f0922cd 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -11,9 +11,9 @@ export type Margin = { interface IconButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial } diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index 64a91de7b0424945e153563d36a012380796add1..477c2515e320d5e1d4ac2b2cdaf7f8dd587cdde8 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -10,9 +10,9 @@ import { Margin } from "./icon_button" interface TextButtonOptions { layer?: - | ColorScheme["lowest"] - | ColorScheme["middle"] - | ColorScheme["highest"] + | ColorScheme["lowest"] + | ColorScheme["middle"] + | ColorScheme["highest"] color?: keyof ColorScheme["lowest"] margin?: Partial text_properties?: TextProperties diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 1f14d65c8ece43db23b17fa6fec11b5987acb775..bdde221acae12f47711f46ea66c5dcc1fbb21d31 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -181,7 +181,7 @@ export default function assistant(theme: ColorScheme): any { }, }, }), - savedAt: { + saved_at: { margin: { left: 8 }, ...text(theme.highest, "sans", "default", { size: "xs" }), }, diff --git a/styles/src/style_tree/command_palette.ts b/styles/src/style_tree/command_palette.ts index ca9daad95b1d43b829d006a10f9e3899329adabc..289deef54b63b8b71fd24960d88508f7f7d44c98 100644 --- a/styles/src/style_tree/command_palette.ts +++ b/styles/src/style_tree/command_palette.ts @@ -6,7 +6,9 @@ import { toggleable } from "../element" export default function command_palette(theme: ColorScheme): any { const key = toggleable({ base: { - text: text(theme.highest, "mono", "variant", "default", { size: "xs" }), + text: text(theme.highest, "mono", "variant", "default", { + size: "xs", + }), corner_radius: 2, background: background(theme.highest, "on"), padding: { @@ -23,7 +25,9 @@ export default function command_palette(theme: ColorScheme): any { }, state: { active: { - text: text(theme.highest, "mono", "on", "default", { size: "xs" }), + text: text(theme.highest, "mono", "on", "default", { + size: "xs", + }), background: with_opacity(background(theme.highest, "on"), 0.2), }, }, diff --git a/styles/src/style_tree/components.ts b/styles/src/style_tree/components.ts index 6e20486631ab4dabd7b62a765f62ba7292a0b47e..db32712f41b5d0d2f8cf785ea53a2008b6a80cdf 100644 --- a/styles/src/style_tree/components.ts +++ b/styles/src/style_tree/components.ts @@ -1,8 +1,4 @@ -import { - font_families, - font_sizes, - FontWeight, -} from "../common" +import { font_families, font_sizes, FontWeight } from "../common" import { Layer, Styles, StyleSets, Style } from "../theme/color_scheme" function is_style_set(key: any): key is StyleSets { diff --git a/styles/src/style_tree/contact_finder.ts b/styles/src/style_tree/contact_finder.ts index 9f02d450d93d7bc2e8bd9d86535ef82b602a7384..e61d100264bb9713f312c27c446ff36f054e77f5 100644 --- a/styles/src/style_tree/contact_finder.ts +++ b/styles/src/style_tree/contact_finder.ts @@ -17,7 +17,9 @@ export default function contact_finder(theme: ColorScheme): any { background: background(theme.middle, "on"), corner_radius: 6, text: text(theme.middle, "mono"), - placeholder_text: text(theme.middle, "mono", "on", "disabled", { size: "xs" }), + placeholder_text: text(theme.middle, "mono", "on", "disabled", { + size: "xs", + }), selection: theme.players[0], border: border(theme.middle), padding: { diff --git a/styles/src/style_tree/context_menu.ts b/styles/src/style_tree/context_menu.ts index f111225c947af39b7fb25234de3196b61fca5735..af45942d2d56d69dcb4ca799282d71c203223eec 100644 --- a/styles/src/style_tree/context_menu.ts +++ b/styles/src/style_tree/context_menu.ts @@ -29,7 +29,9 @@ export default function context_menu(theme: ColorScheme): any { state: { hovered: { background: background(theme.middle, "hovered"), - label: text(theme.middle, "sans", "hovered", { size: "sm" }), + label: text(theme.middle, "sans", "hovered", { + size: "sm", + }), keystroke: { ...text(theme.middle, "sans", "hovered", { size: "sm", diff --git a/styles/src/style_tree/copilot.ts b/styles/src/style_tree/copilot.ts index 7b0fc5e4ea69ee26f4c750e8a0884d5e418b6799..eee6880e0cfa240ad6b9ce38bc3d7252bf1a427e 100644 --- a/styles/src/style_tree/copilot.ts +++ b/styles/src/style_tree/copilot.ts @@ -2,7 +2,6 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, svg, text } from "./components" import { interactive } from "../element" export default function copilot(theme: ColorScheme): any { - const content_width = 264 const cta_button = @@ -61,7 +60,10 @@ export default function copilot(theme: ColorScheme): any { modal: { title_text: { default: { - ...text(theme.middle, "sans", { size: "xs", weight: "bold" }), + ...text(theme.middle, "sans", { + size: "xs", + weight: "bold", + }), }, }, titlebar: { @@ -163,7 +165,10 @@ export default function copilot(theme: ColorScheme): any { }, hint: { - ...text(theme.middle, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { + size: "xs", + color: "#838994", + }), margin: { top: 6, bottom: 2, @@ -271,7 +276,10 @@ export default function copilot(theme: ColorScheme): any { }, hint: { - ...text(theme.middle, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { + size: "xs", + color: "#838994", + }), margin: { top: 24, bottom: 4, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index 67e67e0cf0a3c7cea7454df486acd1c159669ac5..aeb84f678d41e5f4304d436ef0deec46dc5a6b62 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -191,20 +191,12 @@ export default function editor(theme: ColorScheme): any { item: autocomplete_item, hovered_item: { ...autocomplete_item, - match_highlight: foreground( - theme.middle, - "accent", - "hovered" - ), + match_highlight: foreground(theme.middle, "accent", "hovered"), background: background(theme.middle, "hovered"), }, selected_item: { ...autocomplete_item, - match_highlight: foreground( - theme.middle, - "accent", - "active" - ), + match_highlight: foreground(theme.middle, "accent", "active"), background: background(theme.middle, "active"), }, }, diff --git a/styles/src/style_tree/feedback.ts b/styles/src/style_tree/feedback.ts index ab3a40c148b54a1fd7a0a672df258707cd7f3aab..9217b609299426d5444c9e2703fe843aa98e2350 100644 --- a/styles/src/style_tree/feedback.ts +++ b/styles/src/style_tree/feedback.ts @@ -34,7 +34,9 @@ export default function feedback(theme: ColorScheme): any { }, }), button_margin: 8, - info_text_default: text(theme.highest, "sans", "default", { size: "xs" }), + info_text_default: text(theme.highest, "sans", "default", { + size: "xs", + }), link_text_default: text(theme.highest, "sans", "default", { size: "xs", underline: true, diff --git a/styles/src/style_tree/hover_popover.ts b/styles/src/style_tree/hover_popover.ts index e9a008b3c61426aaeb79beea37872cf56ed8388e..f4695057419d3adb0af4c8f46413574f6817a479 100644 --- a/styles/src/style_tree/hover_popover.ts +++ b/styles/src/style_tree/hover_popover.ts @@ -39,7 +39,9 @@ export default function hover_popover(theme: ColorScheme): any { padding: { top: 4 }, }, prose: text(theme.middle, "sans", { size: "sm" }), - diagnostic_source_highlight: { color: foreground(theme.middle, "accent") }, + diagnostic_source_highlight: { + color: foreground(theme.middle, "accent"), + }, highlight: theme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better } } diff --git a/styles/src/style_tree/incoming_call_notification.ts b/styles/src/style_tree/incoming_call_notification.ts index 91947b9da5cfca5a7eefb1a1ac571fe6a24c4ea9..ca46596e57fc46ca4fef1baa50d342b41f720c8a 100644 --- a/styles/src/style_tree/incoming_call_notification.ts +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -29,7 +29,10 @@ export default function incoming_call_notification( margin: { top: -3 }, }, worktree_roots: { - ...text(theme.middle, "sans", "variant", { size: "xs", weight: "bold" }), + ...text(theme.middle, "sans", "variant", { + size: "xs", + weight: "bold", + }), margin: { top: -3 }, }, button_width: 96, diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index 346ffb7641e8c891cd567046f9716e94db9380fc..6ca37936de7ffaa8dc67398118807b8438b7f1dc 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -173,7 +173,9 @@ export default function project_panel(theme: ColorScheme): any { { default: { background: background(theme.middle, "active"), - text: text(theme.middle, "mono", "disabled", { size: "sm" }), + text: text(theme.middle, "mono", "disabled", { + size: "sm", + }), }, } ), diff --git a/styles/src/style_tree/project_shared_notification.ts b/styles/src/style_tree/project_shared_notification.ts index d93c1d65d50f1f8cc85ca1e3d051b875efa37941..ffda0f4b70812a9ff1c1ea65ac87a280c8836bad 100644 --- a/styles/src/style_tree/project_shared_notification.ts +++ b/styles/src/style_tree/project_shared_notification.ts @@ -29,7 +29,10 @@ export default function project_shared_notification( margin: { top: -3 }, }, worktree_roots: { - ...text(theme.middle, "sans", "variant", { size: "xs", weight: "bold" }), + ...text(theme.middle, "sans", "variant", { + size: "xs", + weight: "bold", + }), margin: { top: -3 }, }, button_width: 96, diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts index da515114e7e33f4e4bd4e110176ed1918ccc4068..df260f95b79dc00a22eb3fa23c31b32d03c0007e 100644 --- a/styles/src/style_tree/search.ts +++ b/styles/src/style_tree/search.ts @@ -33,7 +33,10 @@ export default function search(theme: ColorScheme): any { return { // TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive - match_background: with_opacity(foreground(theme.highest, "accent"), 0.4), + match_background: with_opacity( + foreground(theme.highest, "accent"), + 0.4 + ), option_button: toggleable({ base: interactive({ base: { diff --git a/styles/src/style_tree/simple_message_notification.ts b/styles/src/style_tree/simple_message_notification.ts index e9567e235a4bb7dbfbc56f8cb49e5df3e5391ee8..0b5c1e0c294dc9b6f96afb9614a4841b65e2281c 100644 --- a/styles/src/style_tree/simple_message_notification.ts +++ b/styles/src/style_tree/simple_message_notification.ts @@ -2,9 +2,7 @@ import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive } from "../element" -export default function simple_message_notification( - theme: ColorScheme -): any { +export default function simple_message_notification(theme: ColorScheme): any { const header_padding = 8 return { diff --git a/styles/src/style_tree/toolbar_dropdown_menu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts index df5cc094c10619d06bf2c50af2c01def7ba29d69..dc22ac73cf09033a9f446555e871401990b0e81c 100644 --- a/styles/src/style_tree/toolbar_dropdown_menu.ts +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -43,7 +43,9 @@ export default function dropdown_menu(theme: ColorScheme): any { state: { hovered: { background: background(theme.middle, "hovered"), - ...text(theme.middle, "sans", "hovered", { size: "sm" }), + ...text(theme.middle, "sans", "hovered", { + size: "sm", + }), }, }, }), diff --git a/styles/src/style_tree/update_notification.ts b/styles/src/style_tree/update_notification.ts index 48581a5d210dc2948d415c6b3919284eba601823..d14e840450f36a39bc705ac6018563bc1bf4ad81 100644 --- a/styles/src/style_tree/update_notification.ts +++ b/styles/src/style_tree/update_notification.ts @@ -2,7 +2,6 @@ import { ColorScheme } from "../theme/color_scheme" import { foreground, text } from "./components" import { interactive } from "../element" - export default function update_notification(theme: ColorScheme): any { const header_padding = 8 diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts index 0d99ad3f77150077cb56d56629b8bb6fb6688124..fad8dfe23523ba3b1654072251adf3b466963906 100644 --- a/styles/src/style_tree/welcome.ts +++ b/styles/src/style_tree/welcome.ts @@ -32,7 +32,12 @@ export default function welcome(theme: ColorScheme): any { return { page_width: 320, - logo: svg(foreground(theme.highest, "default"), "icons/logo_96.svg", 64, 64), + logo: svg( + foreground(theme.highest, "default"), + "icons/logo_96.svg", + 64, + 64 + ), logo_subheading: { ...text(theme.highest, "sans", "variant", { size: "md" }), margin: { @@ -54,7 +59,10 @@ export default function welcome(theme: ColorScheme): any { }, checkbox_group: { border: border(theme.highest, "variant"), - background: with_opacity(background(theme.highest, "hovered"), 0.25), + background: with_opacity( + background(theme.highest, "hovered"), + 0.25 + ), corner_radius: 4, padding: { left: 12, @@ -77,11 +85,21 @@ export default function welcome(theme: ColorScheme): any { left: 7, right: 7, }, - ...text(theme.highest, "sans", "default", interactive_text_size), + ...text( + theme.highest, + "sans", + "default", + interactive_text_size + ), }, state: { hovered: { - ...text(theme.highest, "sans", "default", interactive_text_size), + ...text( + theme.highest, + "sans", + "default", + interactive_text_size + ), background: background(theme.highest, "hovered"), }, }, @@ -106,7 +124,12 @@ export default function welcome(theme: ColorScheme): any { ...text(theme.highest, "sans", interactive_text_size), // Also supports margin, container, border, etc. }, - icon: svg(foreground(theme.highest, "on"), "icons/check_12.svg", 12, 12), + icon: svg( + foreground(theme.highest, "on"), + "icons/check_12.svg", + 12, + 12 + ), default: { ...checkbox_base, background: background(theme.highest, "default"), diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts index 82b05eab91f86c1c539c53588af1bf94e987360e..0326de414abea6e189cae93a1321fe0a8c0f4fa0 100644 --- a/styles/src/style_tree/workspace.ts +++ b/styles/src/style_tree/workspace.ts @@ -140,18 +140,10 @@ export default function workspace(theme: ColorScheme): any { state: { hovered: { color: foreground(theme.highest, "on", "hovered"), - background: background( - theme.highest, - "on", - "hovered" - ), + background: background(theme.highest, "on", "hovered"), }, disabled: { - color: foreground( - theme.highest, - "on", - "disabled" - ), + color: foreground(theme.highest, "on", "disabled"), }, }, }), @@ -170,11 +162,7 @@ export default function workspace(theme: ColorScheme): any { state: { hovered: { color: foreground(theme.highest, "on", "hovered"), - background: background( - theme.highest, - "on", - "hovered" - ), + background: background(theme.highest, "on", "hovered"), }, }, }), @@ -194,6 +182,9 @@ export default function workspace(theme: ColorScheme): any { width: 400, margin: { right: 10, bottom: 10 }, }, - drop_target_overlay_color: with_opacity(foreground(theme.lowest, "variant"), 0.5), + drop_target_overlay_color: with_opacity( + foreground(theme.lowest, "variant"), + 0.5 + ), } } diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index a8bf807ee0a4a533eabc971e2505e3de42e5a854..148d600713e6b92db3689a3e7d181f6c9b31332f 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -291,7 +291,10 @@ function build_default_syntax(color_scheme: ColorScheme): Syntax { return default_syntax } -function merge_syntax(default_syntax: Syntax, color_scheme: ColorScheme): Syntax { +function merge_syntax( + default_syntax: Syntax, + color_scheme: ColorScheme +): Syntax { if (!color_scheme.syntax) { return default_syntax } diff --git a/styles/src/theme/tokens/color_scheme.ts b/styles/src/theme/tokens/color_scheme.ts index 57793cf8dd052ad0228d4a72850c9e2ec2bfc7a4..a8ce4ec2d252de5f8ec13963c5f41ecbb8ab3424 100644 --- a/styles/src/theme/tokens/color_scheme.ts +++ b/styles/src/theme/tokens/color_scheme.ts @@ -10,9 +10,9 @@ import { SyntaxHighlightStyle, ThemeSyntax, } from "../color_scheme" -import { LayerToken, layerToken } from "./layer" -import { PlayersToken, playersToken } from "./players" -import { colorToken } from "./token" +import { LayerToken, layer_token } from "./layer" +import { PlayersToken, players_token } from "./players" +import { color_token } from "./token" import { Syntax } from "../syntax" import editor from "../../style_tree/editor" @@ -64,13 +64,11 @@ function syntax_highlight_style_color_tokens( if (!syntax[style_key] || typeof syntax[style_key] === "function") return acc const { color } = syntax[style_key] as Required - return { ...acc, [style_key]: colorToken(style_key, color) } + return { ...acc, [style_key]: color_token(style_key, color) } }, {} as ThemeSyntaxColorTokens) } -const syntax_tokens = ( - theme: ColorScheme -): ColorSchemeTokens["syntax"] => { +const syntax_tokens = (theme: ColorScheme): ColorSchemeTokens["syntax"] => { const syntax = editor(theme).syntax return syntax_highlight_style_color_tokens(syntax) @@ -88,12 +86,12 @@ export function theme_tokens(theme: ColorScheme): ColorSchemeTokens { value: theme.is_light ? "light" : "dark", type: TokenTypes.OTHER, }, - lowest: layerToken(theme.lowest, "lowest"), - middle: layerToken(theme.middle, "middle"), - highest: layerToken(theme.highest, "highest"), + lowest: layer_token(theme.lowest, "lowest"), + middle: layer_token(theme.middle, "middle"), + highest: layer_token(theme.highest, "highest"), popover_shadow: popover_shadow_token(theme), modal_shadow: modal_shadow_token(theme), - players: playersToken(theme), + players: players_token(theme), syntax: syntax_tokens(theme), } } diff --git a/styles/src/theme/tokens/players.ts b/styles/src/theme/tokens/players.ts index 68a8e676a71bb7e81d8134c75f77c05289ac4e62..545a712ff123e12aaa2dd32a9645ea64001dd061 100644 --- a/styles/src/theme/tokens/players.ts +++ b/styles/src/theme/tokens/players.ts @@ -6,10 +6,7 @@ export type PlayerToken = Record<"selection" | "cursor", SingleColorToken> export type PlayersToken = Record -function build_player_token( - theme: ColorScheme, - index: number -): PlayerToken { +function build_player_token(theme: ColorScheme, index: number): PlayerToken { const player_number = index.toString() as keyof Players return { diff --git a/styles/src/themes/one/one-dark.ts b/styles/src/themes/one/one-dark.ts index 1241668cc2a2434e3c5d6971e6eddf189a11a6f6..f672b892ee040e60745f3d0f8bd6875c743ed46a 100644 --- a/styles/src/themes/one/one-dark.ts +++ b/styles/src/themes/one/one-dark.ts @@ -25,7 +25,8 @@ export const theme: ThemeConfig = { author: "simurai", appearance: ThemeAppearance.Dark, license_type: ThemeLicenseType.MIT, - license_url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", + license_url: + "https://github.com/atom/atom/tree/master/packages/one-dark-ui", license_file: `${__dirname}/LICENSE`, input_color: { neutral: chroma diff --git a/styles/src/utils/snake_case.ts b/styles/src/utils/snake_case.ts index 38c8a90a9e864177dcea255fa5b87a87da5a9607..cdd9684752826c5606b0e0273b5286bca57c85e0 100644 --- a/styles/src/utils/snake_case.ts +++ b/styles/src/utils/snake_case.ts @@ -5,8 +5,8 @@ import { snakeCase } from "case-anything" // Typescript magic to convert any string from camelCase to snake_case at compile time type SnakeCase = S extends string ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S + ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` + : S : S type SnakeCased = { From 8bff641cc4a4ef61a03b4a87814b8184efa6efa5 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 11:47:58 -0400 Subject: [PATCH 036/169] Organize and update dotfiles --- styles/.eslintrc.js | 35 ----------------------------------- styles/.prettierrc | 6 ++++++ styles/package.json | 19 ++++--------------- 3 files changed, 10 insertions(+), 50 deletions(-) create mode 100644 styles/.prettierrc diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js index 0b4af9dcbc29895d3e83589972106da88d401c8a..485ff73d10441b93e118bbf1a6a89ba5a5f3a8d1 100644 --- a/styles/.eslintrc.js +++ b/styles/.eslintrc.js @@ -29,40 +29,5 @@ module.exports = { rules: { "linebreak-style": ["error", "unix"], semi: ["error", "never"], - "@typescript-eslint/naming-convention": [ - "warn", - { - selector: ["property", "variableLike", "memberLike", "method"], - format: ["snake_case"], - }, - { - selector: ["typeLike"], - format: ["PascalCase"], - }, - ], - "import/no-restricted-paths": [ - "error", - { - zones: [ - { - target: [ - "./src/component/*", - "./src/element/*", - "./src/styleTree/*", - "./src/system/*", - "./src/theme/*", - "./src/themes/*", - "./src/utils/*", - ], - from: [ - "./src/types/styleTree.ts", - "./src/types/element.ts", - "./src/types/property.ts", - ], - message: "Import from `@types` instead", - }, - ], - }, - ], }, } diff --git a/styles/.prettierrc b/styles/.prettierrc new file mode 100644 index 0000000000000000000000000000000000000000..b83ccdda6a71debf8e212f52dd8cc288dd5b521b --- /dev/null +++ b/styles/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 +} diff --git a/styles/package.json b/styles/package.json index 1b90b81048faf590625091b92089d7b40c4adc9f..d82bbb7e817675ffde132e31922691ba7a117fea 100644 --- a/styles/package.json +++ b/styles/package.json @@ -1,8 +1,8 @@ { "name": "styles", "version": "1.0.0", - "description": "", - "main": "index.js", + "description": "Typescript app that builds Zed's themes", + "main": "./src/build_themes.ts", "scripts": { "build": "ts-node ./src/build_themes.ts", "build-licenses": "ts-node ./src/build_licenses.ts", @@ -10,15 +10,13 @@ "build-types": "ts-node ./src/build_types.ts", "test": "vitest" }, - "author": "", + "author": "Zed Industries (https://github.com/zed-industries/)", "license": "ISC", "dependencies": { "@tokens-studio/types": "^0.2.3", "@types/chroma-js": "^2.4.0", "@types/node": "^18.14.1", "ayu": "^8.0.1", - "bezier-easing": "^2.1.0", - "case-anything": "^2.1.10", "chroma-js": "^2.4.2", "deepmerge": "^4.3.0", "json-schema-to-typescript": "^13.0.2", @@ -26,22 +24,13 @@ "ts-deepmerge": "^6.0.3", "ts-node": "^10.9.1", "utility-types": "^3.10.0", - "vitest": "^0.32.0" - }, - "prettier": { - "semi": false, - "printWidth": 80, - "htmlWhitespaceSensitivity": "strict", - "tabWidth": 4 - }, - "devDependencies": { + "vitest": "^0.32.0", "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", "@vitest/coverage-v8": "^0.32.0", "eslint": "^8.43.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-snakecasejs": "^2.2.0", "typescript": "^5.1.5" } } From d285d56fe31ff70ed66af502f59bba64ee606154 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 11:48:17 -0400 Subject: [PATCH 037/169] Update package-lock.json --- styles/package-lock.json | 341 +++++---------------------------------- 1 file changed, 37 insertions(+), 304 deletions(-) diff --git a/styles/package-lock.json b/styles/package-lock.json index 6f3e98a35b481933fbdc17c2af7658ac7a2ee047..3f73a0b4e533899055b2a03e60938eb384e1e583 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -12,34 +12,28 @@ "@tokens-studio/types": "^0.2.3", "@types/chroma-js": "^2.4.0", "@types/node": "^18.14.1", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", + "@vitest/coverage-v8": "^0.32.0", "ayu": "^8.0.1", - "bezier-easing": "^2.1.0", - "case-anything": "^2.1.10", "chroma-js": "^2.4.2", "deepmerge": "^4.3.0", + "eslint": "^8.43.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", "json-schema-to-typescript": "^13.0.2", "toml": "^3.0.0", "ts-deepmerge": "^6.0.3", "ts-node": "^10.9.1", + "typescript": "^5.1.5", "utility-types": "^3.10.0", "vitest": "^0.32.0" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", - "@vitest/coverage-v8": "^0.32.0", - "eslint": "^8.43.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-snakecasejs": "^2.2.0", - "typescript": "^5.1.5" } }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -48,7 +42,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -77,8 +70,7 @@ "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -110,7 +102,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -125,7 +116,6 @@ "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -134,7 +124,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -157,7 +146,6 @@ "version": "8.43.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -166,7 +154,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -180,7 +167,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -192,14 +178,12 @@ "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, "engines": { "node": ">=8" } @@ -208,7 +192,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -230,7 +213,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -258,7 +240,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -271,7 +252,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -280,7 +260,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -293,7 +272,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz", "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.3", "fast-glob": "^3.2.12", @@ -312,8 +290,7 @@ "node_modules/@pkgr/utils/node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, "node_modules/@tokens-studio/types": { "version": "0.2.3", @@ -370,8 +347,7 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" }, "node_modules/@types/json-schema": { "version": "7.0.12", @@ -381,8 +357,7 @@ "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/lodash": { "version": "4.14.195", @@ -407,14 +382,12 @@ "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", - "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.60.1", @@ -448,7 +421,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", - "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.60.1", "@typescript-eslint/types": "5.60.1", @@ -475,7 +447,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.60.1", "@typescript-eslint/visitor-keys": "5.60.1" @@ -492,7 +463,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", - "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "5.60.1", "@typescript-eslint/utils": "5.60.1", @@ -519,7 +489,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -532,7 +501,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.60.1", "@typescript-eslint/visitor-keys": "5.60.1", @@ -559,7 +527,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -585,7 +552,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -598,7 +564,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -607,7 +572,6 @@ "version": "5.60.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.60.1", "eslint-visitor-keys": "^3.3.0" @@ -624,7 +588,6 @@ "version": "0.32.0", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.32.0.tgz", "integrity": "sha512-VXXlWq9X/NbsoP/l/CHLBjutsFFww1UY1qEhzGjn/DY7Tqe+z0Nu8XKc8im/XUAmjiWsh2XV7sy/F0IKAl4eaw==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", "@bcoe/v8-coverage": "^0.2.3", @@ -724,7 +687,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -741,7 +703,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -791,7 +752,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -804,7 +764,6 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -823,7 +782,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { "node": ">=8" } @@ -832,7 +790,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -850,7 +807,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -876,7 +832,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -899,16 +854,10 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/bezier-easing": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", - "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" - }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, "engines": { "node": ">=0.6" } @@ -922,7 +871,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, "dependencies": { "big-integer": "^1.6.44" }, @@ -943,7 +891,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -955,7 +902,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, "dependencies": { "run-applescript": "^5.0.0" }, @@ -978,7 +924,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -996,22 +941,10 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } }, - "node_modules/case-anything": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", - "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -1033,7 +966,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1049,7 +981,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1092,7 +1023,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1103,8 +1033,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -1132,8 +1061,7 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/create-require": { "version": "1.1.1", @@ -1144,7 +1072,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1204,8 +1131,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/deepmerge": { "version": "4.3.0", @@ -1219,7 +1145,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, "dependencies": { "bundle-name": "^3.0.0", "default-browser-id": "^3.0.0", @@ -1237,7 +1162,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, "dependencies": { "bplist-parser": "^0.2.0", "untildify": "^4.0.0" @@ -1253,7 +1177,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, "engines": { "node": ">=12" }, @@ -1265,7 +1188,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -1289,7 +1211,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -1301,7 +1222,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -1313,7 +1233,6 @@ "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -1326,7 +1245,6 @@ "version": "1.21.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "available-typed-arrays": "^1.0.5", @@ -1374,7 +1292,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -1388,7 +1305,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, "dependencies": { "has": "^1.0.3" } @@ -1397,7 +1313,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -1494,7 +1409,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -1506,7 +1420,6 @@ "version": "8.43.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -1562,7 +1475,6 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.11.0", @@ -1573,7 +1485,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -1582,7 +1493,6 @@ "version": "3.5.5", "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz", "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==", - "dev": true, "dependencies": { "debug": "^4.3.4", "enhanced-resolve": "^5.12.0", @@ -1608,7 +1518,6 @@ "version": "13.2.0", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz", "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==", - "dev": true, "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.2.11", @@ -1627,7 +1536,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, "engines": { "node": ">=12" }, @@ -1639,7 +1547,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -1656,7 +1563,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -1665,7 +1571,6 @@ "version": "2.27.5", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -1694,7 +1599,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -1703,7 +1607,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -1715,25 +1618,14 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-snakecasejs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-snakecasejs/-/eslint-plugin-snakecasejs-2.2.0.tgz", - "integrity": "sha512-vdQHT2VvzPpJHHPAVXjtyAZ/CfiJqNCa2d9kn6XMapWBN2Uio/nzL957TooNa6gumlHabBAhB5eSNmqwHgu8gA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/eslint-scope": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -1749,7 +1641,6 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1761,7 +1652,6 @@ "version": "9.5.2", "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", - "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", @@ -1778,7 +1668,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -1790,7 +1679,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -1802,7 +1690,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -1828,7 +1715,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.1", @@ -1863,8 +1749,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -1875,7 +1760,6 @@ "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1891,7 +1775,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1902,20 +1785,17 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -1924,7 +1804,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -1936,7 +1815,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1948,7 +1826,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1964,7 +1841,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -1976,14 +1852,12 @@ "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -2009,14 +1883,12 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -2034,7 +1906,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2051,7 +1922,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -2077,7 +1947,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "engines": { "node": ">=10" }, @@ -2089,7 +1958,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -2105,7 +1973,6 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", - "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -2136,7 +2003,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -2166,7 +2032,6 @@ "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2181,7 +2046,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -2196,7 +2060,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -2216,7 +2079,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2227,26 +2089,22 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -2258,7 +2116,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2267,7 +2124,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2276,7 +2132,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -2288,7 +2143,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2300,7 +2154,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2312,7 +2165,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -2326,14 +2178,12 @@ "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "node_modules/human-signals": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, "engines": { "node": ">=14.18.0" } @@ -2342,7 +2192,6 @@ "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, "engines": { "node": ">= 4" } @@ -2351,7 +2200,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2367,7 +2215,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -2390,7 +2237,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -2404,7 +2250,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -2418,7 +2263,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -2430,7 +2274,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -2446,7 +2289,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2458,7 +2300,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -2470,7 +2311,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2485,7 +2325,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, "bin": { "is-docker": "cli.js" }, @@ -2519,7 +2358,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, "dependencies": { "is-docker": "^3.0.0" }, @@ -2537,7 +2375,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2549,7 +2386,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -2558,7 +2394,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2573,7 +2408,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2587,7 +2421,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -2603,7 +2436,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -2615,7 +2447,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -2627,7 +2458,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2642,7 +2472,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -2657,7 +2486,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -2676,7 +2504,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -2688,7 +2515,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -2700,7 +2526,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, "bin": { "is-docker": "cli.js" }, @@ -2714,14 +2539,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, "engines": { "node": ">=8" } @@ -2730,7 +2553,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", @@ -2744,7 +2566,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -2758,7 +2579,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -2816,20 +2636,17 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -2846,7 +2663,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -2870,7 +2686,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -2889,8 +2704,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/loupe": { "version": "2.3.6", @@ -2934,7 +2748,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -2949,7 +2762,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -2988,14 +2800,12 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -3004,7 +2814,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -3017,7 +2826,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, "engines": { "node": ">=12" }, @@ -3101,14 +2909,12 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/natural-compare-lite": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" }, "node_modules/next-tick": { "version": "1.1.0", @@ -3124,7 +2930,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, "dependencies": { "path-key": "^4.0.0" }, @@ -3139,7 +2944,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, "engines": { "node": ">=12" }, @@ -3159,7 +2963,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3168,7 +2971,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -3177,7 +2979,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3195,7 +2996,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3220,7 +3020,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, "dependencies": { "mimic-fn": "^4.0.0" }, @@ -3235,7 +3034,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, "dependencies": { "default-browser": "^4.0.0", "define-lazy-prop": "^3.0.0", @@ -3253,7 +3051,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -3284,7 +3081,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -3299,7 +3095,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3314,7 +3109,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, @@ -3326,7 +3120,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3338,7 +3131,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -3355,7 +3147,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3363,14 +3154,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -3397,7 +3186,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -3446,7 +3234,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -3482,7 +3269,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } @@ -3491,7 +3277,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3516,7 +3301,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -3533,7 +3317,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -3550,7 +3333,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -3559,7 +3341,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -3568,7 +3349,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3578,7 +3358,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -3608,7 +3387,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, "dependencies": { "execa": "^5.0.0" }, @@ -3623,7 +3401,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -3646,7 +3423,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, "engines": { "node": ">=10.17.0" } @@ -3655,7 +3431,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -3667,7 +3442,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -3676,7 +3450,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "dependencies": { "path-key": "^3.0.0" }, @@ -3688,7 +3461,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -3703,7 +3475,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, "engines": { "node": ">=6" } @@ -3712,7 +3483,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -3735,7 +3505,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -3763,7 +3532,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -3775,7 +3543,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -3784,7 +3551,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -3802,14 +3568,12 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3818,7 +3582,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3845,7 +3608,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3862,7 +3624,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3876,7 +3637,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3890,7 +3650,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3902,7 +3661,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "engines": { "node": ">=4" } @@ -3911,7 +3669,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, "engines": { "node": ">=12" }, @@ -3923,7 +3680,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -3946,7 +3702,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -3958,7 +3713,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3970,7 +3724,6 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dev": true, "dependencies": { "@pkgr/utils": "^2.3.1", "tslib": "^2.5.0" @@ -3985,14 +3738,12 @@ "node_modules/synckit/node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -4001,7 +3752,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -4014,8 +3764,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/thenify": { "version": "3.3.1", @@ -4078,7 +3827,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, "engines": { "node": ">=12" }, @@ -4090,7 +3838,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -4157,7 +3904,6 @@ "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -4168,14 +3914,12 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -4195,7 +3939,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -4215,7 +3958,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -4227,7 +3969,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -4258,7 +3999,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -4273,7 +4013,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, "engines": { "node": ">=8" } @@ -4282,7 +4021,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -4304,7 +4042,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -4318,7 +4055,6 @@ "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -4482,7 +4218,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -4497,7 +4232,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -4513,7 +4247,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", From 52113282340b093319df0b5310209fb745ad3f74 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 11:56:14 -0400 Subject: [PATCH 038/169] Delete snake_case.ts --- styles/src/utils/snake_case.ts | 36 ---------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 styles/src/utils/snake_case.ts diff --git a/styles/src/utils/snake_case.ts b/styles/src/utils/snake_case.ts deleted file mode 100644 index cdd9684752826c5606b0e0273b5286bca57c85e0..0000000000000000000000000000000000000000 --- a/styles/src/utils/snake_case.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { snakeCase } from "case-anything" - -// https://stackoverflow.com/questions/60269936/typescript-convert-generic-object-from-snake-to-camel-case - -// Typescript magic to convert any string from camelCase to snake_case at compile time -type SnakeCase = S extends string - ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S - : S - -type SnakeCased = { - [Property in keyof Type as SnakeCase]: SnakeCased -} - -export default function snakeCaseTree(object: T): SnakeCased { - const snakeObject: any = {} // eslint-disable-line @typescript-eslint/no-explicit-any - for (const key in object) { - snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = - snakeCaseValue(object[key]) - } - return snakeObject -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function snakeCaseValue(value: any): any { - if (typeof value === "object") { - if (Array.isArray(value)) { - return value.map(snakeCaseValue) - } else { - return snakeCaseTree(value) - } - } else { - return value - } -} From ba80c5327858e57205b4637c5269a6c933d07040 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 29 Jun 2023 11:35:49 -0700 Subject: [PATCH 039/169] Avoid redundant FS scans when LSPs changed watched files * Don't scan directories if they were already loaded. * Do less work when FS events occur inside unloaded directories. --- crates/project/src/project_tests.rs | 9 ++ crates/project/src/worktree.rs | 154 +++++++++++++++------------- 2 files changed, 93 insertions(+), 70 deletions(-) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 478fad74a9d2a1b6d33c5c29117ab5667eafe64b..16e706a77eeb9b4a92a368d7bb653639d24e9814 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -596,6 +596,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon ); }); + let prev_read_dir_count = fs.read_dir_call_count(); + // Keep track of the FS events reported to the language server. let fake_server = fake_servers.next().await.unwrap(); let file_changes = Arc::new(Mutex::new(Vec::new())); @@ -607,6 +609,12 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon register_options: serde_json::to_value( lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![ + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( + "/the-root/Cargo.toml".to_string(), + ), + kind: None, + }, lsp::FileSystemWatcher { glob_pattern: lsp::GlobPattern::String( "/the-root/src/*.{rs,c}".to_string(), @@ -638,6 +646,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon cx.foreground().run_until_parked(); assert_eq!(mem::take(&mut *file_changes.lock()), &[]); + assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); // Now the language server has asked us to watch an ignored directory path, // so we recursively load it. diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index be3bcd05fa2e10241a1bc614599dc23014220260..2084b98381b49dfe411c3f77fe4e35a7645d0959 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3071,17 +3071,20 @@ impl BackgroundScanner { path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => { let Ok(path_prefix) = path_prefix else { break }; + log::trace!("adding path prefix {:?}", path_prefix); - self.forcibly_load_paths(&[path_prefix.clone()]).await; + let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await; + if did_scan { + let abs_path = + { + let mut state = self.state.lock(); + state.path_prefixes_to_scan.insert(path_prefix.clone()); + state.snapshot.abs_path.join(&path_prefix) + }; - let abs_path = - { - let mut state = self.state.lock(); - state.path_prefixes_to_scan.insert(path_prefix.clone()); - state.snapshot.abs_path.join(path_prefix) - }; - if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { - self.process_events(vec![abs_path]).await; + if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { + self.process_events(vec![abs_path]).await; + } } } @@ -3097,10 +3100,13 @@ impl BackgroundScanner { } } - async fn process_scan_request(&self, request: ScanRequest, scanning: bool) -> bool { + async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool { log::debug!("rescanning paths {:?}", request.relative_paths); - let root_path = self.forcibly_load_paths(&request.relative_paths).await; + request.relative_paths.sort_unstable(); + self.forcibly_load_paths(&request.relative_paths).await; + + let root_path = self.state.lock().snapshot.abs_path.clone(); let root_canonical_path = match self.fs.canonicalize(&root_path).await { Ok(path) => path, Err(err) => { @@ -3108,10 +3114,9 @@ impl BackgroundScanner { return false; } }; - let abs_paths = request .relative_paths - .into_iter() + .iter() .map(|path| { if path.file_name().is_some() { root_canonical_path.join(path) @@ -3120,12 +3125,19 @@ impl BackgroundScanner { } }) .collect::>(); - self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None) - .await; + + self.reload_entries_for_paths( + root_path, + root_canonical_path, + &request.relative_paths, + abs_paths, + None, + ) + .await; self.send_status_update(scanning, Some(request.done)) } - async fn process_events(&mut self, abs_paths: Vec) { + async fn process_events(&mut self, mut abs_paths: Vec) { log::debug!("received fs events {:?}", abs_paths); let root_path = self.state.lock().snapshot.abs_path.clone(); @@ -3137,25 +3149,61 @@ impl BackgroundScanner { } }; - let (scan_job_tx, scan_job_rx) = channel::unbounded(); - let paths = self - .reload_entries_for_paths( + let mut relative_paths = Vec::with_capacity(abs_paths.len()); + let mut unloaded_relative_paths = Vec::new(); + abs_paths.sort_unstable(); + abs_paths.dedup_by(|a, b| a.starts_with(&b)); + abs_paths.retain(|abs_path| { + let snapshot = &self.state.lock().snapshot; + { + let relative_path: Arc = + if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { + path.into() + } else { + log::error!( + "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}", + ); + return false; + }; + + let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { + snapshot + .entry_for_path(parent) + .map_or(false, |entry| entry.kind == EntryKind::Dir) + }); + if !parent_dir_is_loaded { + unloaded_relative_paths.push(relative_path); + log::debug!("ignoring event {abs_path:?} within unloaded directory"); + return false; + } + + relative_paths.push(relative_path); + true + } + }); + + if !relative_paths.is_empty() { + let (scan_job_tx, scan_job_rx) = channel::unbounded(); + self.reload_entries_for_paths( root_path, root_canonical_path, + &relative_paths, abs_paths, Some(scan_job_tx.clone()), ) .await; - drop(scan_job_tx); - self.scan_dirs(false, scan_job_rx).await; + drop(scan_job_tx); + self.scan_dirs(false, scan_job_rx).await; - let (scan_job_tx, scan_job_rx) = channel::unbounded(); - self.update_ignore_statuses(scan_job_tx).await; - self.scan_dirs(false, scan_job_rx).await; + let (scan_job_tx, scan_job_rx) = channel::unbounded(); + self.update_ignore_statuses(scan_job_tx).await; + self.scan_dirs(false, scan_job_rx).await; + } { let mut state = self.state.lock(); - state.reload_repositories(&paths, self.fs.as_ref()); + relative_paths.extend(unloaded_relative_paths); + state.reload_repositories(&relative_paths, self.fs.as_ref()); state.snapshot.completed_scan_id = state.snapshot.scan_id; for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { state.scanned_dirs.remove(&entry_id); @@ -3165,12 +3213,11 @@ impl BackgroundScanner { self.send_status_update(false, None); } - async fn forcibly_load_paths(&self, paths: &[Arc]) -> Arc { - let root_path; + async fn forcibly_load_paths(&self, paths: &[Arc]) -> bool { let (scan_job_tx, mut scan_job_rx) = channel::unbounded(); { let mut state = self.state.lock(); - root_path = state.snapshot.abs_path.clone(); + let root_path = state.snapshot.abs_path.clone(); for path in paths { for ancestor in path.ancestors() { if let Some(entry) = state.snapshot.entry_for_path(ancestor) { @@ -3201,8 +3248,8 @@ impl BackgroundScanner { while let Some(job) = scan_job_rx.next().await { self.scan_dir(&job).await.log_err(); } - self.state.lock().paths_to_scan.clear(); - root_path + + mem::take(&mut self.state.lock().paths_to_scan).len() > 0 } async fn scan_dirs( @@ -3475,7 +3522,7 @@ impl BackgroundScanner { .expect("channel is unbounded"); } } else { - log::debug!("defer scanning directory {:?} {:?}", entry.path, entry.kind); + log::debug!("defer scanning directory {:?}", entry.path); entry.kind = EntryKind::UnloadedDir; } } @@ -3490,26 +3537,10 @@ impl BackgroundScanner { &self, root_abs_path: Arc, root_canonical_path: PathBuf, - mut abs_paths: Vec, + relative_paths: &[Arc], + abs_paths: Vec, scan_queue_tx: Option>, - ) -> Vec> { - let mut event_paths = Vec::>::with_capacity(abs_paths.len()); - abs_paths.sort_unstable(); - abs_paths.dedup_by(|a, b| a.starts_with(&b)); - abs_paths.retain(|abs_path| { - if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { - event_paths.push(path.into()); - true - } else { - log::error!( - "unexpected event {:?} for root path {:?}", - abs_path, - root_canonical_path - ); - false - } - }); - + ) { let metadata = futures::future::join_all( abs_paths .iter() @@ -3538,30 +3569,15 @@ impl BackgroundScanner { // Remove any entries for paths that no longer exist or are being recursively // refreshed. Do this before adding any new entries, so that renames can be // detected regardless of the order of the paths. - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { if matches!(metadata, Ok(None)) || doing_recursive_update { log::trace!("remove path {:?}", path); state.remove_path(path); } } - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { - if let (Some(parent), true) = (path.parent(), doing_recursive_update) { - if state - .snapshot - .entry_for_path(parent) - .map_or(true, |entry| entry.kind != EntryKind::Dir) - { - log::debug!( - "ignoring event {path:?} within unloaded directory {:?}", - parent - ); - continue; - } - } - + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { let abs_path: Arc = root_abs_path.join(&path).into(); - match metadata { Ok(Some((metadata, canonical_path))) => { let ignore_stack = state @@ -3624,12 +3640,10 @@ impl BackgroundScanner { util::extend_sorted( &mut state.changed_paths, - event_paths.iter().cloned(), + relative_paths.iter().cloned(), usize::MAX, Ord::cmp, ); - - event_paths } fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { From 764968e7d0edb74a8f7bc34112bea435ded4c380 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 29 Jun 2023 14:40:00 -0400 Subject: [PATCH 040/169] Re-add missing active state --- styles/src/style_tree/project_panel.ts | 36 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts index 6ca37936de7ffaa8dc67398118807b8438b7f1dc..d1024778f1d1423eba265ca7a4ee8be7706975cc 100644 --- a/styles/src/style_tree/project_panel.ts +++ b/styles/src/style_tree/project_panel.ts @@ -47,7 +47,7 @@ export default function project_panel(theme: ColorScheme): any { icon_color: foreground(theme.middle, "variant"), icon_size: 7, icon_spacing: 5, - text: text(theme.middle, "mono", "variant", { size: "sm" }), + text: text(theme.middle, "sans", "variant", { size: "sm" }), status: { ...git_status, }, @@ -64,28 +64,42 @@ export default function project_panel(theme: ColorScheme): any { ) const unselected_hovered_style = merge( base_properties, + { background: background(theme.middle, "hovered") }, unselected?.hovered ?? {}, - { background: background(theme.middle, "variant", "hovered") } ) const unselected_clicked_style = merge( base_properties, + { background: background(theme.middle, "pressed"), } + , unselected?.clicked ?? {}, - { background: background(theme.middle, "variant", "pressed") } ) const selected_default_style = merge( base_properties, + { + background: background(theme.lowest), + text: text(theme.lowest, "sans", { size: "sm" }), + }, selected_style?.default ?? {}, - { background: background(theme.middle) } + ) const selected_hovered_style = merge( base_properties, + { + background: background(theme.lowest, "hovered"), + text: text(theme.lowest, "sans", { size: "sm" }), + + }, selected_style?.hovered ?? {}, - { background: background(theme.middle, "variant", "hovered") } + ) const selected_clicked_style = merge( base_properties, + { + background: background(theme.lowest, "pressed"), + text: text(theme.lowest, "sans", { size: "sm" }), + }, selected_style?.clicked ?? {}, - { background: background(theme.middle, "variant", "pressed") } + ) return toggleable({ @@ -148,14 +162,14 @@ export default function project_panel(theme: ColorScheme): any { entry: default_entry, dragged_entry: { ...default_entry.inactive.default, - text: text(theme.middle, "mono", "on", { size: "sm" }), + text: text(theme.middle, "sans", "on", { size: "sm" }), background: with_opacity(background(theme.middle, "on"), 0.9), border: border(theme.middle), }, ignored_entry: entry( { default: { - text: text(theme.middle, "mono", "disabled"), + text: text(theme.middle, "sans", "disabled"), }, }, { @@ -167,13 +181,13 @@ export default function project_panel(theme: ColorScheme): any { cut_entry: entry( { default: { - text: text(theme.middle, "mono", "disabled"), + text: text(theme.middle, "sans", "disabled"), }, }, { default: { background: background(theme.middle, "active"), - text: text(theme.middle, "mono", "disabled", { + text: text(theme.middle, "sans", "disabled", { size: "sm", }), }, @@ -181,7 +195,7 @@ export default function project_panel(theme: ColorScheme): any { ), filename_editor: { background: background(theme.middle, "on"), - text: text(theme.middle, "mono", "on", { size: "sm" }), + text: text(theme.middle, "sans", "on", { size: "sm" }), selection: theme.players[0], }, } From 8609ccdcf7fffc3a69b823fe0c0f2aaf20aed462 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 29 Jun 2023 11:55:25 -0700 Subject: [PATCH 041/169] Add test coverage for FS events happening inside unloaded dirs --- crates/fs/src/fs.rs | 11 ++++++++- crates/project/src/worktree.rs | 35 +++++++++++++--------------- crates/project/src/worktree_tests.rs | 17 ++++++++++++++ 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index e487b64c4e97591c8bb56a17ed01a0d091a2f57c..592e6c9a5318b09cb92e01be484d099dfa526c99 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -388,6 +388,7 @@ struct FakeFsState { event_txs: Vec>>, events_paused: bool, buffered_events: Vec, + metadata_call_count: usize, read_dir_call_count: usize, } @@ -538,6 +539,7 @@ impl FakeFs { buffered_events: Vec::new(), events_paused: false, read_dir_call_count: 0, + metadata_call_count: 0, }), }) } @@ -774,10 +776,16 @@ impl FakeFs { result } + /// How many `read_dir` calls have been issued. pub fn read_dir_call_count(&self) -> usize { self.state.lock().read_dir_call_count } + /// How many `metadata` calls have been issued. + pub fn metadata_call_count(&self) -> usize { + self.state.lock().metadata_call_count + } + async fn simulate_random_delay(&self) { self.executor .upgrade() @@ -1098,7 +1106,8 @@ impl Fs for FakeFs { async fn metadata(&self, path: &Path) -> Result> { self.simulate_random_delay().await; let path = normalize_path(path); - let state = self.state.lock(); + let mut state = self.state.lock(); + state.metadata_call_count += 1; if let Some((mut entry, _)) = state.try_read_path(&path, false) { let is_symlink = entry.lock().is_symlink(); if is_symlink { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2084b98381b49dfe411c3f77fe4e35a7645d0959..4eb7aed2e55b29d1827c0861db3cb816b9293a5d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3774,25 +3774,22 @@ impl BackgroundScanner { // Scan any directories that were previously ignored and weren't // previously scanned. - if was_ignored - && !entry.is_ignored - && !entry.is_external - && entry.kind == EntryKind::UnloadedDir - { - job.scan_queue - .try_send(ScanJob { - abs_path: abs_path.clone(), - path: entry.path.clone(), - ignore_stack: child_ignore_stack.clone(), - scan_queue: job.scan_queue.clone(), - ancestor_inodes: self - .state - .lock() - .snapshot - .ancestor_inodes_for_path(&entry.path), - is_external: false, - }) - .unwrap(); + if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() { + let state = self.state.lock(); + if state.should_scan_directory(&entry) { + job.scan_queue + .try_send(ScanJob { + abs_path: abs_path.clone(), + path: entry.path.clone(), + ignore_stack: child_ignore_stack.clone(), + scan_queue: job.scan_queue.clone(), + ancestor_inodes: state + .snapshot + .ancestor_inodes_for_path(&entry.path), + is_external: false, + }) + .unwrap(); + } } job.ignore_queue diff --git a/crates/project/src/worktree_tests.rs b/crates/project/src/worktree_tests.rs index 553c5e2ccafa754623fb0ee85d0c63eccc8ea9e9..f908d702eb22aeb7dfb02eb4300611b7d22fbd73 100644 --- a/crates/project/src/worktree_tests.rs +++ b/crates/project/src/worktree_tests.rs @@ -454,6 +454,10 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { "b1.js": "b1", "b2.js": "b2", }, + "c": { + "c1.js": "c1", + "c2.js": "c2", + } }, }, "two": { @@ -521,6 +525,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -564,6 +569,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -578,6 +584,17 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { // Only the newly-expanded directory is scanned. assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1); }); + + // No work happens when files and directories change within an unloaded directory. + let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count(); + fs.create_dir("/root/one/node_modules/c/lib".as_ref()) + .await + .unwrap(); + cx.foreground().run_until_parked(); + assert_eq!( + fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count, + 0 + ); } #[gpui::test] From 922d8f30d60b1dc4d28b8274a2bb7773f91bcc4f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 29 Jun 2023 12:01:59 -0700 Subject: [PATCH 042/169] Tweak debug log message when ignoring fs events --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4eb7aed2e55b29d1827c0861db3cb816b9293a5d..20e693770f45f686a880b5aebd542eb74a96202e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3172,8 +3172,8 @@ impl BackgroundScanner { .map_or(false, |entry| entry.kind == EntryKind::Dir) }); if !parent_dir_is_loaded { + log::debug!("ignoring event {relative_path:?} within unloaded directory"); unloaded_relative_paths.push(relative_path); - log::debug!("ignoring event {abs_path:?} within unloaded directory"); return false; } From 8a3b515f56a9045dde8503440ff6c20134d85779 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 30 May 2023 16:41:57 +0300 Subject: [PATCH 043/169] Initial protocol check commit --- crates/editor/src/editor.rs | 25 +++++ crates/lsp/src/lsp.rs | 8 ++ crates/project/src/lsp_command.rs | 153 +++++++++++++++++++++++++++++- crates/project/src/project.rs | 50 ++++++++++ 4 files changed, 234 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8adf98f1bc76e4754ad55a6b18b7f9d96d1e2fd0..038b62f499943e47dbb158839d0ce97ec1473387 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2151,6 +2151,10 @@ impl Editor { } } + if let Some(hints_task) = this.request_inlay_hints(cx) { + hints_task.detach_and_log_err(cx); + } + if had_active_copilot_suggestion { this.refresh_copilot_suggestions(true, cx); if !this.has_active_copilot_suggestion(cx) { @@ -2577,6 +2581,27 @@ impl Editor { } } + // TODO kb proper inlay hints handling + fn request_inlay_hints(&self, cx: &mut ViewContext) -> Option>> { + let project = self.project.as_ref()?; + let position = self.selections.newest_anchor().head(); + let (buffer, _) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx)?; + + let end = buffer.read(cx).len(); + let inlay_hints_task = project.update(cx, |project, cx| { + project.inlay_hints(buffer.clone(), 0..end, cx) + }); + + Some(cx.spawn(|_, _| async move { + let inlay_hints = inlay_hints_task.await?; + dbg!(inlay_hints); + Ok(()) + })) + } + fn trigger_on_type_formatting( &self, input: String, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 1293408324fff36c38385da8a91263d3e1e41171..95c7dc5fa9f38b38afd4e5266898d762a8de448c 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -388,6 +388,9 @@ impl LanguageServer { resolve_support: None, ..WorkspaceSymbolClientCapabilities::default() }), + inlay_hint: Some(InlayHintWorkspaceClientCapabilities { + refresh_support: Default::default(), + }), ..Default::default() }), text_document: Some(TextDocumentClientCapabilities { @@ -429,6 +432,11 @@ impl LanguageServer { content_format: Some(vec![MarkupKind::Markdown]), ..Default::default() }), + // TODO kb add the resolution at least + inlay_hint: Some(InlayHintClientCapabilities { + resolve_support: None, + dynamic_registration: Some(false), + }), ..Default::default() }), experimental: Some(json!({ diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8435de71e2f420e479035a5d21b36e850db29834..9e6b3038a3842c9c04d160bce6c3a54c82a41da5 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,6 +1,7 @@ use crate::{ - DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectTransaction, + DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, + InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, + MarkupContent, Project, ProjectTransaction, }; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -126,6 +127,10 @@ pub(crate) struct OnTypeFormatting { pub push_to_history: bool, } +pub(crate) struct InlayHints { + pub range: Range, +} + pub(crate) struct FormattingOptions { tab_size: u32, } @@ -1780,3 +1785,147 @@ impl LspCommand for OnTypeFormatting { message.buffer_id } } + +#[async_trait(?Send)] +impl LspCommand for InlayHints { + type Response = Vec; + type LspRequest = lsp::InlayHintRequest; + type ProtoRequest = proto::OnTypeFormatting; + + fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { + let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false }; + match inlay_hint_provider { + lsp::OneOf::Left(enabled) => *enabled, + lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities { + lsp::InlayHintServerCapabilities::Options(_) => true, + // TODO kb there could be dynamic registrations, resolve options + lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false, + }, + } + } + + fn to_lsp( + &self, + path: &Path, + buffer: &Buffer, + _: &Arc, + _: &AppContext, + ) -> lsp::InlayHintParams { + lsp::InlayHintParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), + }, + range: range_to_lsp(self.range.to_point_utf16(buffer)), + work_done_progress_params: Default::default(), + } + } + + async fn response_from_lsp( + self, + message: Option>, + _: ModelHandle, + buffer: ModelHandle, + _: LanguageServerId, + cx: AsyncAppContext, + ) -> Result> { + cx.read(|cx| { + let origin_buffer = buffer.read(cx); + Ok(message + .unwrap_or_default() + .into_iter() + .map(|lsp_hint| InlayHint { + position: origin_buffer.anchor_after( + origin_buffer + .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), + ), + label: match lsp_hint.label { + lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), + lsp::InlayHintLabel::LabelParts(lsp_parts) => InlayHintLabel::LabelParts( + lsp_parts + .into_iter() + .map(|label_part| InlayHintLabelPart { + value: label_part.value, + tooltip: label_part.tooltip.map(|tooltip| match tooltip { + lsp::InlayHintLabelPartTooltip::String(s) => { + InlayHintLabelPartTooltip::String(s) + } + lsp::InlayHintLabelPartTooltip::MarkupContent( + markup_content, + ) => InlayHintLabelPartTooltip::MarkupContent( + MarkupContent { + kind: format!("{:?}", markup_content.kind), + value: markup_content.value, + }, + ), + }), + location: label_part.location.map(|lsp_location| { + let target_start = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.start), + Bias::Left, + ); + let target_end = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.end), + Bias::Left, + ); + Location { + buffer: buffer.clone(), + range: origin_buffer.anchor_after(target_start) + ..origin_buffer.anchor_before(target_end), + } + }), + }) + .collect(), + ), + }, + kind: lsp_hint.kind.map(|kind| format!("{kind:?}")), + tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { + lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), + lsp::InlayHintTooltip::MarkupContent(markup_content) => { + InlayHintTooltip::MarkupContent(MarkupContent { + kind: format!("{:?}", markup_content.kind), + value: markup_content.value, + }) + } + }), + }) + .collect()) + }) + } + + fn to_proto(&self, _: u64, _: &Buffer) -> proto::OnTypeFormatting { + todo!("TODO kb") + } + + async fn from_proto( + _: proto::OnTypeFormatting, + _: ModelHandle, + _: ModelHandle, + _: AsyncAppContext, + ) -> Result { + todo!("TODO kb") + } + + fn response_to_proto( + _: Vec, + _: &mut Project, + _: PeerId, + _: &clock::Global, + _: &mut AppContext, + ) -> proto::OnTypeFormattingResponse { + todo!("TODO kb") + } + + async fn response_from_proto( + self, + _: proto::OnTypeFormattingResponse, + _: ModelHandle, + _: ModelHandle, + _: AsyncAppContext, + ) -> Result> { + todo!("TODO kb") + } + + fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 { + message.buffer_id + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 270ee32babc91a4b75ee91bfc7d9e9d902dcb278..a7ab9e90686aebd345762f6351d60aeeb8f9b330 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -325,6 +325,45 @@ pub struct Location { pub range: Range, } +#[derive(Debug)] +pub struct InlayHint { + pub position: Anchor, + pub label: InlayHintLabel, + pub kind: Option, + pub tooltip: Option, +} + +#[derive(Debug)] +pub enum InlayHintLabel { + String(String), + LabelParts(Vec), +} + +#[derive(Debug)] +pub struct InlayHintLabelPart { + pub value: String, + pub tooltip: Option, + pub location: Option, +} + +#[derive(Debug)] +pub enum InlayHintTooltip { + String(String), + MarkupContent(MarkupContent), +} + +#[derive(Debug)] +pub enum InlayHintLabelPartTooltip { + String(String), + MarkupContent(MarkupContent), +} + +#[derive(Debug)] +pub struct MarkupContent { + pub kind: String, + pub value: String, +} + #[derive(Debug, Clone)] pub struct LocationLink { pub origin: Option, @@ -4837,6 +4876,17 @@ impl Project { ) } + pub fn inlay_hints( + &self, + buffer_handle: ModelHandle, + range: Range, + cx: &mut ModelContext, + ) -> Task>> { + let buffer = buffer_handle.read(cx); + let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); + self.request_lsp(buffer_handle, InlayHints { range }, cx) + } + #[allow(clippy::type_complexity)] pub fn search( &self, From 79b97f9e753011c7242bcf644109c209542e9f0b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 30 May 2023 20:36:36 +0300 Subject: [PATCH 044/169] Stub initial hint requests --- crates/editor/src/editor.rs | 112 +++++++++++++++++++++++++++------- crates/editor/src/element.rs | 6 ++ crates/lsp/src/lsp.rs | 2 +- crates/project/src/project.rs | 30 +++++++-- 4 files changed, 122 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 038b62f499943e47dbb158839d0ce97ec1473387..a583bdee0eed1393522a5315848599a9c57a45b4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -70,7 +70,10 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; +use parking_lot::RwLock; +use project::{ + FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction, +}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -87,7 +90,10 @@ use std::{ num::NonZeroU32, ops::{Deref, DerefMut, Range}, path::Path, - sync::Arc, + sync::{ + atomic::{self, AtomicUsize}, + Arc, + }, time::{Duration, Instant}, }; pub use sum_tree::Bias; @@ -535,6 +541,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, + inlay_hints: Arc, _subscriptions: Vec, } @@ -1151,6 +1158,47 @@ impl CopilotState { } } +#[derive(Debug, Default)] +struct InlayHintState { + hints: RwLock>, + last_updated_timestamp: AtomicUsize, + hints_generation: AtomicUsize, +} + +impl InlayHintState { + pub fn new_timestamp(&self) -> usize { + self.hints_generation + .fetch_add(1, atomic::Ordering::Release) + + 1 + } + + pub fn read(&self) -> Vec { + self.hints.read().clone() + } + + pub fn update_if_newer(&self, new_hints: Vec, new_timestamp: usize) { + let last_updated_timestamp = self.last_updated_timestamp.load(atomic::Ordering::Acquire); + if last_updated_timestamp < new_timestamp { + let mut guard = self.hints.write(); + match self.last_updated_timestamp.compare_exchange( + last_updated_timestamp, + new_timestamp, + atomic::Ordering::AcqRel, + atomic::Ordering::Acquire, + ) { + Ok(_) => *guard = new_hints, + Err(other_value) => { + if other_value < new_timestamp { + self.last_updated_timestamp + .store(new_timestamp, atomic::Ordering::Release); + *guard = new_hints; + } + } + } + } + } +} + #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, @@ -1340,6 +1388,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), + inlay_hints: Arc::new(InlayHintState::default()), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -1366,6 +1415,8 @@ impl Editor { } this.report_editor_event("open", None, cx); + // this.update_inlay_hints(cx); + this } @@ -2151,10 +2202,6 @@ impl Editor { } } - if let Some(hints_task) = this.request_inlay_hints(cx) { - hints_task.detach_and_log_err(cx); - } - if had_active_copilot_suggestion { this.refresh_copilot_suggestions(true, cx); if !this.has_active_copilot_suggestion(cx) { @@ -2581,25 +2628,45 @@ impl Editor { } } - // TODO kb proper inlay hints handling - fn request_inlay_hints(&self, cx: &mut ViewContext) -> Option>> { - let project = self.project.as_ref()?; + fn update_inlay_hints(&self, cx: &mut ViewContext) { + if self.mode != EditorMode::Full { + return; + } let position = self.selections.newest_anchor().head(); - let (buffer, _) = self + let Some((buffer, _)) = self .buffer .read(cx) - .text_anchor_for_position(position.clone(), cx)?; + .text_anchor_for_position(position.clone(), cx) else { return }; + + let generator_buffer = buffer.clone(); + let inlay_hints_storage = Arc::clone(&self.inlay_hints); + // TODO kb should this come from external things like transaction counter instead? + // This way we can reuse tasks result for the same timestamp? The counter has to be global among all buffer changes & other reloads. + let new_timestamp = self.inlay_hints.new_timestamp(); + + // TODO kb this would not work until the language server is ready, how to wait for it? + // TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal? + // need to be able to not to start new tasks, if current one is running on the same state already. + cx.spawn(|editor, mut cx| async move { + let task = editor.update(&mut cx, |editor, cx| { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + // TODO kb use visible_lines as a range instead? + let end = generator_buffer.read(cx).len(); + project.inlay_hints(generator_buffer, 0..end, cx) + }) + }) + })?; - let end = buffer.read(cx).len(); - let inlay_hints_task = project.update(cx, |project, cx| { - project.inlay_hints(buffer.clone(), 0..end, cx) - }); + if let Some(task) = task { + // TODO kb contexts everywhere + let new_hints = task.await?; + inlay_hints_storage.update_if_newer(new_hints, new_timestamp); + } - Some(cx.spawn(|_, _| async move { - let inlay_hints = inlay_hints_task.await?; - dbg!(inlay_hints); - Ok(()) - })) + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } fn trigger_on_type_formatting( @@ -6640,7 +6707,10 @@ impl Editor { ) -> Option { self.start_transaction_at(Instant::now(), cx); update(self, cx); - self.end_transaction_at(Instant::now(), cx) + let transaction_id = self.end_transaction_at(Instant::now(), cx); + // TODO kb is this the right idea? Maybe instead we should react on `BufferEvent::Edited`? + self.update_inlay_hints(cx); + transaction_id } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6525e7fc222843fdfa04a94317f4a2a95f6dadcf..ee0bd3e8b474b1b787b92efb0cc32cab78de6c09 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -879,6 +879,7 @@ impl EditorElement { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { let row = start_row + ix as u32; line_with_invisibles.draw( + editor, layout, row, scroll_top, @@ -1794,6 +1795,7 @@ impl LineWithInvisibles { fn draw( &self, + editor: &mut Editor, layout: &LayoutState, row: u32, scroll_top: f32, @@ -1817,6 +1819,10 @@ impl LineWithInvisibles { cx, ); + // TODO kb bad: cloning happens very frequently, check the timestamp first + let new_hints = editor.inlay_hints.read(); + // dbg!(new_hints); + self.draw_invisibles( &selection_ranges, layout, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 95c7dc5fa9f38b38afd4e5266898d762a8de448c..798f35ba5c5b9a92f603e3d0bf347e45c46c52ac 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -389,7 +389,7 @@ impl LanguageServer { ..WorkspaceSymbolClientCapabilities::default() }), inlay_hint: Some(InlayHintWorkspaceClientCapabilities { - refresh_support: Default::default(), + refresh_support: Some(true), }), ..Default::default() }), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a7ab9e90686aebd345762f6351d60aeeb8f9b330..46da79c5b7afde4a689703a86cd3280f05ccdac8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -325,7 +325,7 @@ pub struct Location { pub range: Range, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InlayHint { pub position: Anchor, pub label: InlayHintLabel, @@ -333,32 +333,32 @@ pub struct InlayHint { pub tooltip: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintLabel { String(String), LabelParts(Vec), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, pub location: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum InlayHintLabelPartTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MarkupContent { pub kind: String, pub value: String, @@ -2810,6 +2810,24 @@ impl Project { }) .detach(); + language_server + .on_request::({ + dbg!("!!!!!!!!!!!!!!"); + let this = this.downgrade(); + move |params, cx| async move { + // TODO kb: trigger an event, to call on every open editor + // TODO kb does not get called now, why? + dbg!("@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + let _this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + dbg!(params); + Ok(()) + } + }) + .detach(); + let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token.clone(); From 7a268b1cf6df780f4e196d2aaff958e72f67eaee Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 31 May 2023 21:11:24 +0300 Subject: [PATCH 045/169] Improve obvious faults --- crates/editor/src/editor.rs | 94 ++++++++++++++++++++++++----------- crates/editor/src/element.rs | 4 +- crates/project/src/project.rs | 79 ++++++++++++++--------------- 3 files changed, 106 insertions(+), 71 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a583bdee0eed1393522a5315848599a9c57a45b4..cc3368f677ad8676292453db038a36aa84096c54 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1178,6 +1178,7 @@ impl InlayHintState { pub fn update_if_newer(&self, new_hints: Vec, new_timestamp: usize) { let last_updated_timestamp = self.last_updated_timestamp.load(atomic::Ordering::Acquire); + dbg!(last_updated_timestamp, new_timestamp, new_hints.len()); if last_updated_timestamp < new_timestamp { let mut guard = self.hints.write(); match self.last_updated_timestamp.compare_exchange( @@ -1330,12 +1331,22 @@ impl Editor { let soft_wrap_mode_override = (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); - let mut project_subscription = None; + let mut project_subscriptions = Vec::new(); if mode == EditorMode::Full && buffer.read(cx).is_singleton() { if let Some(project) = project.as_ref() { - project_subscription = Some(cx.observe(project, |_, _, cx| { + project_subscriptions.push(cx.observe(project, |_, _, cx| { cx.emit(Event::TitleChanged); - })) + })); + project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { + match event { + project::Event::LanguageServerReady(_) => { + dbg!("@@@@@@@@@@@@@ ReceiveD event"); + editor.update_inlay_hints(cx); + } + _ => {} + }; + cx.notify() + })); } } @@ -1399,9 +1410,7 @@ impl Editor { ], }; - if let Some(project_subscription) = project_subscription { - this._subscriptions.push(project_subscription); - } + this._subscriptions.extend(project_subscriptions); this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); @@ -1415,8 +1424,6 @@ impl Editor { } this.report_editor_event("open", None, cx); - // this.update_inlay_hints(cx); - this } @@ -2644,14 +2651,12 @@ impl Editor { // This way we can reuse tasks result for the same timestamp? The counter has to be global among all buffer changes & other reloads. let new_timestamp = self.inlay_hints.new_timestamp(); - // TODO kb this would not work until the language server is ready, how to wait for it? // TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal? // need to be able to not to start new tasks, if current one is running on the same state already. cx.spawn(|editor, mut cx| async move { let task = editor.update(&mut cx, |editor, cx| { editor.project.as_ref().map(|project| { project.update(cx, |project, cx| { - // TODO kb use visible_lines as a range instead? let end = generator_buffer.read(cx).len(); project.inlay_hints(generator_buffer, 0..end, cx) }) @@ -6707,10 +6712,7 @@ impl Editor { ) -> Option { self.start_transaction_at(Instant::now(), cx); update(self, cx); - let transaction_id = self.end_transaction_at(Instant::now(), cx); - // TODO kb is this the right idea? Maybe instead we should react on `BufferEvent::Edited`? - self.update_inlay_hints(cx); - transaction_id + self.end_transaction_at(Instant::now(), cx) } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { @@ -7190,7 +7192,7 @@ impl Editor { event: &multi_buffer::Event, cx: &mut ViewContext, ) { - match event { + let update_inlay_hints = match event { multi_buffer::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); @@ -7198,30 +7200,62 @@ impl Editor { self.update_visible_copilot_suggestion(cx); } cx.emit(Event::BufferEdited); + true } multi_buffer::Event::ExcerptsAdded { buffer, predecessor, excerpts, - } => cx.emit(Event::ExcerptsAdded { - buffer: buffer.clone(), - predecessor: *predecessor, - excerpts: excerpts.clone(), - }), + } => { + cx.emit(Event::ExcerptsAdded { + buffer: buffer.clone(), + predecessor: *predecessor, + excerpts: excerpts.clone(), + }); + // TODO kb wrong? + false + } multi_buffer::Event::ExcerptsRemoved { ids } => { - cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }) - } - multi_buffer::Event::Reparsed => cx.emit(Event::Reparsed), - multi_buffer::Event::DirtyChanged => cx.emit(Event::DirtyChanged), - multi_buffer::Event::Saved => cx.emit(Event::Saved), - multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged), - multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged), - multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged), - multi_buffer::Event::Closed => cx.emit(Event::Closed), + cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); + false + } + multi_buffer::Event::Reparsed => { + cx.emit(Event::Reparsed); + true + } + multi_buffer::Event::DirtyChanged => { + cx.emit(Event::DirtyChanged); + true + } + multi_buffer::Event::Saved => { + cx.emit(Event::Saved); + false + } + multi_buffer::Event::FileHandleChanged => { + cx.emit(Event::TitleChanged); + true + } + multi_buffer::Event::Reloaded => { + cx.emit(Event::TitleChanged); + true + } + multi_buffer::Event::DiffBaseChanged => { + cx.emit(Event::DiffBaseChanged); + true + } + multi_buffer::Event::Closed => { + cx.emit(Event::Closed); + false + } multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); + false } - _ => {} + _ => true, + }; + + if update_inlay_hints { + self.update_inlay_hints(cx); } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ee0bd3e8b474b1b787b92efb0cc32cab78de6c09..2a58f959d3d8ea97e394986c9150ee7c989661f3 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1819,9 +1819,9 @@ impl LineWithInvisibles { cx, ); - // TODO kb bad: cloning happens very frequently, check the timestamp first + // TODO kb bad: syscalls + cloning happen very frequently, check the timestamp first let new_hints = editor.inlay_hints.read(); - // dbg!(new_hints); + dbg!(new_hints.last()); self.draw_invisibles( &selection_ranges, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 46da79c5b7afde4a689703a86cd3280f05ccdac8..5625efddc2c0e44e10c8944731de92e62a43c653 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -254,6 +254,7 @@ pub enum Event { LanguageServerAdded(LanguageServerId), LanguageServerRemoved(LanguageServerId), LanguageServerLog(LanguageServerId, String), + LanguageServerReady(LanguageServerId), Notification(String), ActiveEntryChanged(Option), WorktreeAdded, @@ -2814,15 +2815,18 @@ impl Project { .on_request::({ dbg!("!!!!!!!!!!!!!!"); let this = this.downgrade(); - move |params, cx| async move { - // TODO kb: trigger an event, to call on every open editor + move |params, mut cx| async move { // TODO kb does not get called now, why? - dbg!("@@@@@@@@@@@@@@@@@@@@@@@@@@"); + dbg!("#########################"); - let _this = this + let this = this .upgrade(&cx) .ok_or_else(|| anyhow!("project dropped"))?; dbg!(params); + this.update(&mut cx, |_, cx| { + dbg!("@@@@@@@@@@@@@ SENT event"); + cx.emit(Event::LanguageServerReady(server_id)); + }); Ok(()) } }) @@ -5477,41 +5481,39 @@ impl Project { let abs_path = worktree_handle.read(cx).abs_path(); for server_id in &language_server_ids { - if let Some(server) = self.language_servers.get(server_id) { - if let LanguageServerState::Running { - server, - watched_paths, - .. - } = server - { - if let Some(watched_paths) = watched_paths.get(&worktree_id) { - let params = lsp::DidChangeWatchedFilesParams { - changes: changes - .iter() - .filter_map(|(path, _, change)| { - if !watched_paths.is_match(&path) { - return None; - } - let typ = match change { - PathChange::Loaded => return None, - PathChange::Added => lsp::FileChangeType::CREATED, - PathChange::Removed => lsp::FileChangeType::DELETED, - PathChange::Updated => lsp::FileChangeType::CHANGED, - PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, - }; - Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), - typ, - }) + if let Some(LanguageServerState::Running { + server, + watched_paths, + .. + }) = self.language_servers.get(server_id) + { + if let Some(watched_paths) = watched_paths.get(&worktree_id) { + let params = lsp::DidChangeWatchedFilesParams { + changes: changes + .iter() + .filter_map(|(path, _, change)| { + if !watched_paths.is_match(&path) { + return None; + } + let typ = match change { + PathChange::Loaded => return None, + PathChange::Added => lsp::FileChangeType::CREATED, + PathChange::Removed => lsp::FileChangeType::DELETED, + PathChange::Updated => lsp::FileChangeType::CHANGED, + PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, + }; + Some(lsp::FileEvent { + uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), + typ, }) - .collect(), - }; + }) + .collect(), + }; - if !params.changes.is_empty() { - server - .notify::(params) - .log_err(); - } + if !params.changes.is_empty() { + server + .notify::(params) + .log_err(); } } } @@ -7385,10 +7387,9 @@ impl Project { self.language_server_ids_for_buffer(buffer, cx) .into_iter() .filter_map(|server_id| { - let server = self.language_servers.get(&server_id)?; if let LanguageServerState::Running { adapter, server, .. - } = server + } = self.language_servers.get(&server_id)? { Some((adapter, server)) } else { From f83cfda9bc4ff4e69f81ea4edf8fe8c59d75c476 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 2 Jun 2023 18:23:37 +0300 Subject: [PATCH 046/169] React on message-less LSP requests properly Co-Authored-By: Julia Risley --- crates/editor/src/editor.rs | 3 +-- crates/editor/src/element.rs | 1 - crates/lsp/src/lsp.rs | 1 + crates/project/src/project.rs | 12 +++--------- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cc3368f677ad8676292453db038a36aa84096c54..6db9644e4e48e4e63607f41721b27a153506918c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1339,8 +1339,7 @@ impl Editor { })); project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { match event { - project::Event::LanguageServerReady(_) => { - dbg!("@@@@@@@@@@@@@ ReceiveD event"); + project::Event::ReloadInlayHints => { editor.update_inlay_hints(cx); } _ => {} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2a58f959d3d8ea97e394986c9150ee7c989661f3..2cf9ff5fe66d327846bfcc1142856720f1e4b60b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1821,7 +1821,6 @@ impl LineWithInvisibles { // TODO kb bad: syscalls + cloning happen very frequently, check the timestamp first let new_hints = editor.inlay_hints.read(); - dbg!(new_hints.last()); self.draw_invisibles( &selection_ranges, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 798f35ba5c5b9a92f603e3d0bf347e45c46c52ac..d8e7efe89b5c9ac680c68c84576a2a03e106c734 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -615,6 +615,7 @@ impl LanguageServer { }) .detach(); } + Err(error) => { log::error!( "error deserializing {} request: {:?}, message: {:?}", diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5625efddc2c0e44e10c8944731de92e62a43c653..913c0bbab1e3d04d98ae22b4bd142ecb4fac9b4b 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -254,7 +254,6 @@ pub enum Event { LanguageServerAdded(LanguageServerId), LanguageServerRemoved(LanguageServerId), LanguageServerLog(LanguageServerId, String), - LanguageServerReady(LanguageServerId), Notification(String), ActiveEntryChanged(Option), WorktreeAdded, @@ -278,6 +277,7 @@ pub enum Event { new_peer_id: proto::PeerId, }, CollaboratorLeft(proto::PeerId), + ReloadInlayHints, } pub enum LanguageServerState { @@ -2813,19 +2813,13 @@ impl Project { language_server .on_request::({ - dbg!("!!!!!!!!!!!!!!"); let this = this.downgrade(); - move |params, mut cx| async move { - // TODO kb does not get called now, why? - dbg!("#########################"); - + move |(), mut cx| async move { let this = this .upgrade(&cx) .ok_or_else(|| anyhow!("project dropped"))?; - dbg!(params); this.update(&mut cx, |_, cx| { - dbg!("@@@@@@@@@@@@@ SENT event"); - cx.emit(Event::LanguageServerReady(server_id)); + cx.emit(Event::ReloadInlayHints); }); Ok(()) } From 387415eb015476c362f28b1887caf7337fb113aa Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 2 Jun 2023 23:04:27 +0300 Subject: [PATCH 047/169] Request hints for all buffers in editor --- crates/editor/src/editor.rs | 177 +++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6db9644e4e48e4e63607f41721b27a153506918c..23e90f5840506c9ab59a82cb2020fe5f17e92efb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,11 +22,11 @@ pub mod test; use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; -use clock::ReplicaId; -use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; +use clock::{Global, ReplicaId}; +use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; @@ -64,6 +64,7 @@ use language::{ use link_go_to_definition::{ hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, }; +use log::error; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -90,10 +91,7 @@ use std::{ num::NonZeroU32, ops::{Deref, DerefMut, Range}, path::Path, - sync::{ - atomic::{self, AtomicUsize}, - Arc, - }, + sync::Arc, time::{Duration, Instant}, }; pub use sum_tree::Bias; @@ -1159,43 +1157,53 @@ impl CopilotState { } #[derive(Debug, Default)] -struct InlayHintState { - hints: RwLock>, - last_updated_timestamp: AtomicUsize, - hints_generation: AtomicUsize, -} +struct InlayHintState(RwLock<(HashMap, Vec)>); impl InlayHintState { - pub fn new_timestamp(&self) -> usize { - self.hints_generation - .fetch_add(1, atomic::Ordering::Release) - + 1 - } - - pub fn read(&self) -> Vec { - self.hints.read().clone() - } - - pub fn update_if_newer(&self, new_hints: Vec, new_timestamp: usize) { - let last_updated_timestamp = self.last_updated_timestamp.load(atomic::Ordering::Acquire); - dbg!(last_updated_timestamp, new_timestamp, new_hints.len()); - if last_updated_timestamp < new_timestamp { - let mut guard = self.hints.write(); - match self.last_updated_timestamp.compare_exchange( - last_updated_timestamp, - new_timestamp, - atomic::Ordering::AcqRel, - atomic::Ordering::Acquire, - ) { - Ok(_) => *guard = new_hints, - Err(other_value) => { - if other_value < new_timestamp { - self.last_updated_timestamp - .store(new_timestamp, atomic::Ordering::Release); - *guard = new_hints; + fn read(&self) -> Vec { + self.0.read().1.clone() + } + + fn is_newer(&self, timestamp: &HashMap) -> bool { + let current_timestamp = self.0.read().0.clone(); + Self::first_timestamp_newer(timestamp, ¤t_timestamp) + } + + fn update_if_newer(&self, new_hints: Vec, new_timestamp: HashMap) { + let mut guard = self.0.write(); + if Self::first_timestamp_newer(&new_timestamp, &guard.0) { + guard.0 = new_timestamp; + guard.1 = new_hints; + } + } + + fn first_timestamp_newer( + first: &HashMap, + second: &HashMap, + ) -> bool { + if first.is_empty() { + false + } else if second.is_empty() { + true + } else { + let mut first_newer = false; + let mut second_has_extra_buffers = false; + for (buffer_id, first_version) in first { + match second.get(buffer_id) { + None => { + second_has_extra_buffers = true; + } + Some(second_version) => { + if second_version.changed_since(&first_version) { + return false; + } else if first_version.changed_since(&second_version) { + first_newer = true; + } } } } + + first_newer || !second_has_extra_buffers } } } @@ -1340,7 +1348,7 @@ impl Editor { project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { match event { project::Event::ReloadInlayHints => { - editor.update_inlay_hints(cx); + editor.try_update_inlay_hints(cx); } _ => {} }; @@ -1930,7 +1938,7 @@ impl Editor { s.set_pending(pending, mode); }); } else { - log::error!("update_selection dispatched with no pending selection"); + error!("update_selection dispatched with no pending selection"); return; } @@ -2634,43 +2642,64 @@ impl Editor { } } - fn update_inlay_hints(&self, cx: &mut ViewContext) { + fn try_update_inlay_hints(&self, cx: &mut ViewContext) { if self.mode != EditorMode::Full { return; } - let position = self.selections.newest_anchor().head(); - let Some((buffer, _)) = self - .buffer - .read(cx) - .text_anchor_for_position(position.clone(), cx) else { return }; - let generator_buffer = buffer.clone(); - let inlay_hints_storage = Arc::clone(&self.inlay_hints); - // TODO kb should this come from external things like transaction counter instead? - // This way we can reuse tasks result for the same timestamp? The counter has to be global among all buffer changes & other reloads. - let new_timestamp = self.inlay_hints.new_timestamp(); - - // TODO kb waiting before the server starts and handling workspace/inlayHint/refresh commands is kind of orthogonal? - // need to be able to not to start new tasks, if current one is running on the same state already. - cx.spawn(|editor, mut cx| async move { - let task = editor.update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - let end = generator_buffer.read(cx).len(); - project.inlay_hints(generator_buffer, 0..end, cx) - }) - }) - })?; + let mut hint_fetch_tasks = Vec::new(); + let new_timestamp = self.buffer().read(cx).all_buffers().into_iter().fold( + HashMap::default(), + |mut buffer_versions, new_buffer| { + let new_buffer_version = new_buffer.read(cx).version(); + match buffer_versions.entry(new_buffer.id()) { + hash_map::Entry::Occupied(mut entry) => { + let entry_version = entry.get(); + if new_buffer_version.changed_since(&entry_version) { + entry.insert(new_buffer_version); + } + } + hash_map::Entry::Vacant(v) => { + v.insert(new_buffer_version); + } + } - if let Some(task) = task { - // TODO kb contexts everywhere - let new_hints = task.await?; - inlay_hints_storage.update_if_newer(new_hints, new_timestamp); - } + hint_fetch_tasks.push(cx.spawn(|editor, mut cx| async move { + let task = editor + .update(&mut cx, |editor, cx| { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + let end = new_buffer.read(cx).len(); + project.inlay_hints(new_buffer, 0..end, cx) + }) + }) + }) + .context("inlay hints fecth task spawn")?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + match task { + Some(task) => Ok(task.await.context("inlay hints fetch task await")?), + None => anyhow::Ok(Vec::new()), + } + })); + + buffer_versions + }, + ); + + let inlay_hints_storage = Arc::clone(&self.inlay_hints); + if inlay_hints_storage.is_newer(&new_timestamp) { + cx.spawn(|_, _| async move { + let mut new_hints = Vec::new(); + for task_result in futures::future::join_all(hint_fetch_tasks).await { + match task_result { + Ok(task_hints) => new_hints.extend(task_hints), + Err(e) => error!("Failed to update hints for buffer: {e:#}"), + } + } + inlay_hints_storage.update_if_newer(new_hints, new_timestamp); + }) + .detach(); + } } fn trigger_on_type_formatting( @@ -6737,7 +6766,7 @@ impl Editor { if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { *end_selections = Some(self.selections.disjoint_anchors()); } else { - log::error!("unexpectedly ended a transaction that wasn't started by this editor"); + error!("unexpectedly ended a transaction that wasn't started by this editor"); } cx.emit(Event::Edited); @@ -7254,7 +7283,7 @@ impl Editor { }; if update_inlay_hints { - self.update_inlay_hints(cx); + self.try_update_inlay_hints(cx); } } From 6e3d1b962a8ba1e9e408f6e4dd12166658ddcc2d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 4 Jun 2023 21:49:22 +0300 Subject: [PATCH 048/169] Draft the initial protobuf changes --- crates/collab/src/rpc.rs | 1 + crates/editor/src/editor.rs | 1 - crates/project/src/lsp_command.rs | 186 +++++++++++++++++++++++++++--- crates/project/src/project.rs | 42 +++++++ crates/rpc/proto/zed.proto | 59 ++++++++++ crates/rpc/src/proto.rs | 4 + 6 files changed, 274 insertions(+), 19 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index a5be6e7d62fd7e0a2da1a66f63e5aba5eff98954..583c708e0a9eae99532e6d8d9604b447b5b7105d 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -226,6 +226,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_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) .add_message_handler(update_buffer_file) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 23e90f5840506c9ab59a82cb2020fe5f17e92efb..63b3c4f6a3b19d7b7b6a743b45db78ce431bf52f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7240,7 +7240,6 @@ impl Editor { predecessor: *predecessor, excerpts: excerpts.clone(), }); - // TODO kb wrong? false } multi_buffer::Event::ExcerptsRemoved { ids } => { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 9e6b3038a3842c9c04d160bce6c3a54c82a41da5..fb01becaf417c917da91251a19c561a8aec5462f 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -3,7 +3,7 @@ use crate::{ InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, MarkupContent, Project, ProjectTransaction, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::proto::{self, PeerId}; use fs::LineEnding; @@ -1790,7 +1790,7 @@ impl LspCommand for OnTypeFormatting { impl LspCommand for InlayHints { type Response = Vec; type LspRequest = lsp::InlayHintRequest; - type ProtoRequest = proto::OnTypeFormatting; + type ProtoRequest = proto::InlayHints; fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false }; @@ -1892,40 +1892,190 @@ impl LspCommand for InlayHints { }) } - fn to_proto(&self, _: u64, _: &Buffer) -> proto::OnTypeFormatting { - todo!("TODO kb") + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints { + proto::InlayHints { + project_id, + buffer_id: buffer.remote_id(), + start: Some(language::proto::serialize_anchor(&self.range.start)), + end: Some(language::proto::serialize_anchor(&self.range.end)), + version: serialize_version(&buffer.version()), + } } async fn from_proto( - _: proto::OnTypeFormatting, + message: proto::InlayHints, _: ModelHandle, - _: ModelHandle, - _: AsyncAppContext, + buffer: ModelHandle, + mut cx: AsyncAppContext, ) -> Result { - todo!("TODO kb") + let start = message + .start + .and_then(language::proto::deserialize_anchor) + .context("invalid start")?; + let end = message + .end + .and_then(language::proto::deserialize_anchor) + .context("invalid end")?; + // TODO kb has it to be multiple versions instead? + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(deserialize_version(&message.version)) + }) + .await?; + + Ok(Self { range: start..end }) } fn response_to_proto( - _: Vec, + response: Vec, _: &mut Project, _: PeerId, - _: &clock::Global, + buffer_version: &clock::Global, _: &mut AppContext, - ) -> proto::OnTypeFormattingResponse { - todo!("TODO kb") + ) -> proto::InlayHintsResponse { + proto::InlayHintsResponse { + hints: response + .into_iter() + .map(|response_hint| proto::InlayHint { + position: Some(language::proto::serialize_anchor(&response_hint.position)), + label: Some(proto::InlayHintLabel { + label: Some(match response_hint.label { + InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s), + InlayHintLabel::LabelParts(label_parts) => { + proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts { + parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart { + value: label_part.value, + tooltip: label_part.tooltip.map(|tooltip| { + let proto_tooltip = match tooltip { + InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s), + InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }), + }; + proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)} + }), + location: label_part.location.map(|location| proto::Location { + start: Some(serialize_anchor(&location.range.start)), + end: Some(serialize_anchor(&location.range.end)), + buffer_id: location.buffer.id() as u64, + }), + }).collect() + }) + } + }), + }), + kind: response_hint.kind, + tooltip: response_hint.tooltip.map(|response_tooltip| { + let proto_tooltip = match response_tooltip { + InlayHintTooltip::String(s) => { + proto::inlay_hint_tooltip::Content::Value(s) + } + InlayHintTooltip::MarkupContent(markup_content) => { + proto::inlay_hint_tooltip::Content::MarkupContent( + proto::MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }, + ) + } + }; + proto::InlayHintTooltip { + content: Some(proto_tooltip), + } + }), + }) + .collect(), + version: serialize_version(buffer_version), + } } async fn response_from_proto( self, - _: proto::OnTypeFormattingResponse, - _: ModelHandle, - _: ModelHandle, - _: AsyncAppContext, + message: proto::InlayHintsResponse, + project: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, ) -> Result> { - todo!("TODO kb") + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(deserialize_version(&message.version)) + }) + .await?; + + let mut hints = Vec::new(); + for message_hint in message.hints { + let hint = InlayHint { + position: message_hint + .position + .and_then(language::proto::deserialize_anchor) + .context("invalid position")?, + label: match message_hint + .label + .and_then(|label| label.label) + .context("missing label")? + { + proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s), + proto::inlay_hint_label::Label::LabelParts(parts) => { + let mut label_parts = Vec::new(); + for part in parts.parts { + label_parts.push(InlayHintLabelPart { + value: part.value, + tooltip: part.tooltip.map(|tooltip| match tooltip.content { + Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => InlayHintLabelPartTooltip::String(s), + Some(proto::inlay_hint_label_part_tooltip::Content::MarkupContent(markup_content)) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }), + None => InlayHintLabelPartTooltip::String(String::new()), + }), + location: match part.location { + Some(location) => { + let target_buffer = project + .update(&mut cx, |this, cx| { + this.wait_for_remote_buffer(location.buffer_id, cx) + }) + .await?; + Some(Location { + range: location + .start + .and_then(language::proto::deserialize_anchor) + .context("invalid start")? + ..location + .end + .and_then(language::proto::deserialize_anchor) + .context("invalid end")?, + buffer: target_buffer, + })}, + None => None, + }, + }); + } + + InlayHintLabel::LabelParts(label_parts) + } + }, + kind: message_hint.kind, + tooltip: message_hint.tooltip.and_then(|tooltip| { + Some(match tooltip.content? { + proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s), + proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => { + InlayHintTooltip::MarkupContent(MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }) + } + }) + }), + }; + + hints.push(hint); + } + + Ok(hints) } - fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 { + fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 { message.buffer_id } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 913c0bbab1e3d04d98ae22b4bd142ecb4fac9b4b..e941eee0329f753d5f9428507b370538b1cdcab6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -525,6 +525,7 @@ impl Project { client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion); 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_reload_buffers); client.add_model_request_handler(Self::handle_synchronize_buffers); client.add_model_request_handler(Self::handle_format_buffers); @@ -6645,6 +6646,47 @@ impl Project { Ok(proto::OnTypeFormattingResponse { transaction }) } + async fn handle_inlay_hints( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let sender_id = envelope.original_sender_id()?; + let buffer = this.update(&mut cx, |this, cx| { + this.opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id)) + })?; + let buffer_version = deserialize_version(&envelope.payload.version); + + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(buffer_version.clone()) + }) + .await + .with_context(|| { + format!( + "waiting for version {:?} for buffer {}", + buffer_version, + buffer.id() + ) + })?; + + let buffer_hints = this + .update(&mut cx, |project, cx| { + let end = buffer.read(cx).len(); + project.inlay_hints(buffer, 0..end, cx) + }) + .await + .context("inlay hints fetch")?; + + Ok(this.update(&mut cx, |project, cx| { + InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx) + })) + } + async fn handle_lsp_command( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2bce1ce1e328bf2302059ad476bc37adb30e079a..6de98c4595847ddc00b1f8c5cad42f5e924102af 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -136,6 +136,9 @@ message Envelope { OnTypeFormattingResponse on_type_formatting_response = 112; UpdateWorktreeSettings update_worktree_settings = 113; + + InlayHints inlay_hints = 114; + InlayHintsResponse inlay_hints_response = 115; } } @@ -705,6 +708,62 @@ message OnTypeFormattingResponse { Transaction transaction = 1; } +message InlayHints { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor start = 3; + Anchor end = 4; + repeated VectorClockEntry version = 5; +} + +message InlayHintsResponse { + repeated InlayHint hints = 1; + repeated VectorClockEntry version = 2; +} + +message InlayHint { + Anchor position = 1; + InlayHintLabel label = 2; + optional string kind = 3; + InlayHintTooltip tooltip = 4; +} + +message InlayHintLabel { + oneof label { + string value = 1; + InlayHintLabelParts label_parts = 2; + } +} + +message InlayHintLabelParts { + repeated InlayHintLabelPart parts = 1; +} + +message InlayHintLabelPart { + string value = 1; + InlayHintLabelPartTooltip tooltip = 2; + Location location = 3; +} + +message InlayHintTooltip { + oneof content { + string value = 1; + MarkupContent markup_content = 2; + } +} + +message InlayHintLabelPartTooltip { + oneof content { + string value = 1; + MarkupContent markup_content = 2; + } +} + +message MarkupContent { + string kind = 1; + string value = 2; +} + message PerformRenameResponse { ProjectTransaction transaction = 2; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 4532e798e72d324c807de07dcb7166edba3e9bac..d917ff10cf493e0775fc4b0c1a96e2e719c7b502 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -198,6 +198,8 @@ messages!( (PerformRenameResponse, Background), (OnTypeFormatting, Background), (OnTypeFormattingResponse, Background), + (InlayHints, Background), + (InlayHintsResponse, Background), (Ping, Foreground), (PrepareRename, Background), (PrepareRenameResponse, Background), @@ -286,6 +288,7 @@ request_messages!( (PerformRename, PerformRenameResponse), (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), + (InlayHints, InlayHintsResponse), (ReloadBuffers, ReloadBuffersResponse), (RequestContact, Ack), (RemoveContact, Ack), @@ -332,6 +335,7 @@ entity_messages!( OpenBufferForSymbol, PerformRename, OnTypeFormatting, + InlayHints, PrepareRename, ReloadBuffers, RemoveProjectCollaborator, From 2ead3de7decce7eb16d348b82fd54035318451fe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 5 Jun 2023 16:55:26 +0300 Subject: [PATCH 049/169] Add basic infrastructure for inlay hints map --- crates/editor/Cargo.toml | 3 +- crates/editor/src/display_map.rs | 42 ++++- crates/editor/src/display_map/block_map.rs | 33 +++- .../src/display_map/editor_addition_map.rs | 176 ++++++++++++++++++ crates/editor/src/display_map/tab_map.rs | 148 +++++++++------ crates/editor/src/display_map/wrap_map.rs | 49 +++-- 6 files changed, 366 insertions(+), 85 deletions(-) create mode 100644 crates/editor/src/display_map/editor_addition_map.rs diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index dcc22202273a5d087a5892f279e1d153b3d9770e..61145e40ff15aacb9a0df10c3f9eb31f5c487244 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -10,7 +10,6 @@ doctest = false [features] test-support = [ - "rand", "copilot/test-support", "text/test-support", "language/test-support", @@ -57,7 +56,7 @@ ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } -rand = { workspace = true, optional = true } +rand = { workspace = true } schemars.workspace = true serde.workspace = true serde_derive.workspace = true diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a594af51a6307bbfda7b421c8a723e0c0e3563ef..5dad501df6731532bd713c8a4ae22eccfbda03f6 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,4 +1,5 @@ mod block_map; +mod editor_addition_map; mod fold_map; mod suggestion_map; mod tab_map; @@ -7,6 +8,7 @@ mod wrap_map; use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; +use editor_addition_map::EditorAdditionMap; use fold_map::{FoldMap, FoldOffset}; use gpui::{ color::Color, @@ -45,6 +47,7 @@ pub struct DisplayMap { buffer_subscription: BufferSubscription, fold_map: FoldMap, suggestion_map: SuggestionMap, + editor_addition_map: EditorAdditionMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, @@ -71,6 +74,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&buffer, cx); let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); let (suggestion_map, snapshot) = SuggestionMap::new(snapshot); + let (editor_addition_map, snapshot) = EditorAdditionMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); @@ -80,6 +84,7 @@ impl DisplayMap { buffer_subscription, fold_map, suggestion_map, + editor_addition_map, tab_map, wrap_map, block_map, @@ -93,11 +98,13 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); - + let (editor_addition_snapshot, edits) = self + .editor_addition_map + .sync(suggestion_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (tab_snapshot, edits) = self - .tab_map - .sync(suggestion_snapshot.clone(), edits, tab_size); + let (tab_snapshot, edits) = + self.tab_map + .sync(editor_addition_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); @@ -107,6 +114,7 @@ impl DisplayMap { buffer_snapshot: self.buffer.read(cx).snapshot(cx), fold_snapshot, suggestion_snapshot, + editor_addition_snapshot, tab_snapshot, wrap_snapshot, block_snapshot, @@ -134,6 +142,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -141,6 +150,7 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -159,6 +169,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -166,6 +177,7 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -183,6 +195,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -201,6 +214,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -249,6 +263,7 @@ impl DisplayMap { let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits, old_suggestion) = self.suggestion_map.replace(new_suggestion, snapshot, edits); + let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -289,6 +304,7 @@ pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, fold_snapshot: fold_map::FoldSnapshot, suggestion_snapshot: suggestion_map::SuggestionSnapshot, + editor_addition_snapshot: editor_addition_map::EditorAdditionSnapshot, tab_snapshot: tab_map::TabSnapshot, wrap_snapshot: wrap_map::WrapSnapshot, block_snapshot: block_map::BlockSnapshot, @@ -366,7 +382,10 @@ impl DisplaySnapshot { fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { let fold_point = self.fold_snapshot.to_fold_point(point, bias); let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - let tab_point = self.tab_snapshot.to_tab_point(suggestion_point); + let editor_addition_point = self + .editor_addition_snapshot + .to_editor_addition_point(suggestion_point); + let tab_point = self.tab_snapshot.to_tab_point(editor_addition_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); DisplayPoint(block_point) @@ -376,7 +395,13 @@ impl DisplaySnapshot { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); - let suggestion_point = self.tab_snapshot.to_suggestion_point(tab_point, bias).0; + let editor_addition_point = self + .tab_snapshot + .to_editor_addition_point(tab_point, bias) + .0; + let suggestion_point = self + .editor_addition_snapshot + .to_suggestion_point(editor_addition_point, bias); let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_point(&self.fold_snapshot) } @@ -790,7 +815,10 @@ impl DisplayPoint { pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); - let suggestion_point = map.tab_snapshot.to_suggestion_point(tab_point, bias).0; + let editor_addition_point = map.tab_snapshot.to_editor_addition_point(tab_point, bias).0; + let suggestion_point = map + .editor_addition_snapshot + .to_suggestion_point(editor_addition_point, bias); let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_offset(&map.fold_snapshot) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b20ecaef1c3724defdf4c093d551aa66615b0f99..a01048670592959125c95632af21d931e2758ea8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -989,6 +989,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { #[cfg(test)] mod tests { use super::*; + use crate::display_map::editor_addition_map::EditorAdditionMap; use crate::display_map::suggestion_map::SuggestionMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::multi_buffer::MultiBuffer; @@ -1032,7 +1033,9 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (editor_addition_map, editor_addition_snapshot) = + EditorAdditionMap::new(suggestion_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(editor_addition_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1179,8 +1182,13 @@ mod tests { fold_map.read(buffer_snapshot, subscription.consume().into_inner()); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, 4.try_into().unwrap()); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (tab_snapshot, tab_edits) = tab_map.sync( + editor_addition_snapshot, + editor_addition_edits, + 4.try_into().unwrap(), + ); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1207,7 +1215,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1279,7 +1288,9 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, tab_size); + let (editor_addition_map, editor_addition_snapshot) = + EditorAdditionMap::new(suggestion_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( @@ -1336,8 +1347,10 @@ mod tests { fold_map.read(buffer_snapshot.clone(), vec![]); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1361,8 +1374,10 @@ mod tests { fold_map.read(buffer_snapshot.clone(), vec![]); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1384,8 +1399,10 @@ mod tests { let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); diff --git a/crates/editor/src/display_map/editor_addition_map.rs b/crates/editor/src/display_map/editor_addition_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..1388f91d306351fc613322dec135c05d6a573cef --- /dev/null +++ b/crates/editor/src/display_map/editor_addition_map.rs @@ -0,0 +1,176 @@ +#![allow(unused)] +// TODO kb + +use std::ops::{Add, AddAssign, Range, Sub}; + +use crate::MultiBufferSnapshot; + +use super::{ + suggestion_map::{SuggestionEdit, SuggestionPoint, SuggestionSnapshot}, + TextHighlights, +}; +use gpui::fonts::HighlightStyle; +use language::{Chunk, Edit, Point, TextSummary}; +use rand::Rng; +use sum_tree::Bias; + +pub struct EditorAdditionMap; + +#[derive(Clone)] +pub struct EditorAdditionSnapshot { + // TODO kb merge these two together + pub suggestion_snapshot: SuggestionSnapshot, + pub version: usize, +} + +pub type EditorAdditionEdit = Edit; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct EditorAdditionOffset(pub usize); + +impl Add for EditorAdditionOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for EditorAdditionOffset { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl AddAssign for EditorAdditionOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct EditorAdditionPoint(pub Point); + +#[derive(Clone)] +pub struct EditorAdditionBufferRows<'a> { + _z: &'a std::marker::PhantomData<()>, +} + +#[derive(Clone)] +pub struct EditorAdditionChunks<'a> { + _z: &'a std::marker::PhantomData<()>, +} + +impl<'a> Iterator for EditorAdditionChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + todo!("TODO kb") + } +} + +impl<'a> Iterator for EditorAdditionBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + todo!("TODO kb") + } +} + +impl EditorAdditionPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } +} + +impl EditorAdditionMap { + pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, EditorAdditionSnapshot) { + todo!("TODO kb") + } + + pub fn sync( + &self, + suggestion_snapshot: SuggestionSnapshot, + suggestion_edits: Vec, + ) -> (EditorAdditionSnapshot, Vec) { + todo!("TODO kb") + } + + pub fn randomly_mutate( + &self, + rng: &mut impl Rng, + ) -> (EditorAdditionSnapshot, Vec) { + todo!("TODO kb") + } +} + +impl EditorAdditionSnapshot { + pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { + todo!("TODO kb") + } + + pub fn to_point(&self, offset: EditorAdditionOffset) -> EditorAdditionPoint { + todo!("TODO kb") + } + + pub fn max_point(&self) -> EditorAdditionPoint { + todo!("TODO kb") + } + + pub fn to_offset(&self, point: EditorAdditionPoint) -> EditorAdditionOffset { + todo!("TODO kb") + } + + pub fn chars_at(&self, start: EditorAdditionPoint) -> impl '_ + Iterator { + Vec::new().into_iter() + } + + pub fn to_suggestion_point(&self, point: EditorAdditionPoint, bias: Bias) -> SuggestionPoint { + todo!("TODO kb") + } + + pub fn to_editor_addition_point(&self, point: SuggestionPoint) -> EditorAdditionPoint { + todo!("TODO kb") + } + + pub fn clip_point(&self, point: EditorAdditionPoint, bias: Bias) -> EditorAdditionPoint { + todo!("TODO kb") + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + todo!("TODO kb") + } + + pub fn buffer_rows<'a>(&'a self, row: u32) -> EditorAdditionBufferRows<'a> { + todo!("TODO kb") + } + + pub fn line_len(&self, row: u32) -> u32 { + todo!("TODO kb") + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + suggestion_highlight: Option, + ) -> EditorAdditionChunks<'a> { + todo!("TODO kb") + } + + #[cfg(test)] + pub fn text(&self) -> String { + todo!("TODO kb") + } +} diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index d97ba4f40be2ebcf3e4d9e0aa5ad7c18d5772c37..b54de1ad651538d629bfc94ba03615268ea74339 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,5 +1,7 @@ use super::{ - suggestion_map::{self, SuggestionChunks, SuggestionEdit, SuggestionPoint, SuggestionSnapshot}, + editor_addition_map::{ + self, EditorAdditionChunks, EditorAdditionEdit, EditorAdditionPoint, EditorAdditionSnapshot, + }, TextHighlights, }; use crate::MultiBufferSnapshot; @@ -14,9 +16,9 @@ const MAX_EXPANSION_COLUMN: u32 = 256; pub struct TabMap(Mutex); impl TabMap { - pub fn new(input: SuggestionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + pub fn new(input: EditorAdditionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { let snapshot = TabSnapshot { - suggestion_snapshot: input, + editor_addition_snapshot: input, tab_size, max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, @@ -32,19 +34,21 @@ impl TabMap { pub fn sync( &self, - suggestion_snapshot: SuggestionSnapshot, - mut suggestion_edits: Vec, + editor_addition_snapshot: EditorAdditionSnapshot, + mut suggestion_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); let mut new_snapshot = TabSnapshot { - suggestion_snapshot, + editor_addition_snapshot, tab_size, max_expansion_column: old_snapshot.max_expansion_column, version: old_snapshot.version, }; - if old_snapshot.suggestion_snapshot.version != new_snapshot.suggestion_snapshot.version { + if old_snapshot.editor_addition_snapshot.version + != new_snapshot.editor_addition_snapshot.version + { new_snapshot.version += 1; } @@ -56,21 +60,21 @@ impl TabMap { // boundary. for suggestion_edit in &mut suggestion_edits { let old_end = old_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.old.end); let old_end_row_successor_offset = - old_snapshot.suggestion_snapshot.to_offset(cmp::min( - SuggestionPoint::new(old_end.row() + 1, 0), - old_snapshot.suggestion_snapshot.max_point(), + old_snapshot.editor_addition_snapshot.to_offset(cmp::min( + EditorAdditionPoint::new(old_end.row() + 1, 0), + old_snapshot.editor_addition_snapshot.max_point(), )); let new_end = new_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.new.end); let mut offset_from_edit = 0; let mut first_tab_offset = None; let mut last_tab_with_changed_expansion_offset = None; - 'outer: for chunk in old_snapshot.suggestion_snapshot.chunks( + 'outer: for chunk in old_snapshot.editor_addition_snapshot.chunks( suggestion_edit.old.end..old_end_row_successor_offset, false, None, @@ -124,16 +128,16 @@ impl TabMap { for suggestion_edit in suggestion_edits { let old_start = old_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.old.start); let old_end = old_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.old.end); let new_start = new_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.new.start); let new_end = new_snapshot - .suggestion_snapshot + .editor_addition_snapshot .to_point(suggestion_edit.new.end); tab_edits.push(TabEdit { old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), @@ -155,7 +159,7 @@ impl TabMap { #[derive(Clone)] pub struct TabSnapshot { - pub suggestion_snapshot: SuggestionSnapshot, + pub editor_addition_snapshot: EditorAdditionSnapshot, pub tab_size: NonZeroU32, pub max_expansion_column: u32, pub version: usize, @@ -163,15 +167,15 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.suggestion_snapshot.buffer_snapshot() + self.editor_addition_snapshot.buffer_snapshot() } pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.to_tab_point(SuggestionPoint::new( + self.to_tab_point(EditorAdditionPoint::new( row, - self.suggestion_snapshot.line_len(row), + self.editor_addition_snapshot.line_len(row), )) .0 .column @@ -185,10 +189,10 @@ impl TabSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let input_start = self.to_suggestion_point(range.start, Bias::Left).0; - let input_end = self.to_suggestion_point(range.end, Bias::Right).0; + let input_start = self.to_editor_addition_point(range.start, Bias::Left).0; + let input_end = self.to_editor_addition_point(range.end, Bias::Right).0; let input_summary = self - .suggestion_snapshot + .editor_addition_snapshot .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; @@ -241,12 +245,12 @@ impl TabSnapshot { suggestion_highlight: Option, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = - self.to_suggestion_point(range.start, Bias::Left); + self.to_editor_addition_point(range.start, Bias::Left); let input_column = input_start.column(); - let input_start = self.suggestion_snapshot.to_offset(input_start); + let input_start = self.editor_addition_snapshot.to_offset(input_start); let input_end = self - .suggestion_snapshot - .to_offset(self.to_suggestion_point(range.end, Bias::Right).0); + .editor_addition_snapshot + .to_offset(self.to_editor_addition_point(range.end, Bias::Right).0); let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { range.end.column() - range.start.column() } else { @@ -254,7 +258,7 @@ impl TabSnapshot { }; TabChunks { - suggestion_chunks: self.suggestion_snapshot.chunks( + editor_addition_chunks: self.editor_addition_snapshot.chunks( input_start..input_end, language_aware, text_highlights, @@ -275,8 +279,8 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> suggestion_map::SuggestionBufferRows { - self.suggestion_snapshot.buffer_rows(row) + pub fn buffer_rows(&self, row: u32) -> editor_addition_map::EditorAdditionBufferRows<'_> { + self.editor_addition_snapshot.buffer_rows(row) } #[cfg(test)] @@ -287,33 +291,37 @@ impl TabSnapshot { } pub fn max_point(&self) -> TabPoint { - self.to_tab_point(self.suggestion_snapshot.max_point()) + self.to_tab_point(self.editor_addition_snapshot.max_point()) } pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { self.to_tab_point( - self.suggestion_snapshot - .clip_point(self.to_suggestion_point(point, bias).0, bias), + self.editor_addition_snapshot + .clip_point(self.to_editor_addition_point(point, bias).0, bias), ) } - pub fn to_tab_point(&self, input: SuggestionPoint) -> TabPoint { + pub fn to_tab_point(&self, input: EditorAdditionPoint) -> TabPoint { let chars = self - .suggestion_snapshot - .chars_at(SuggestionPoint::new(input.row(), 0)); + .editor_addition_snapshot + .chars_at(EditorAdditionPoint::new(input.row(), 0)); let expanded = self.expand_tabs(chars, input.column()); TabPoint::new(input.row(), expanded) } - pub fn to_suggestion_point(&self, output: TabPoint, bias: Bias) -> (SuggestionPoint, u32, u32) { + pub fn to_editor_addition_point( + &self, + output: TabPoint, + bias: Bias, + ) -> (EditorAdditionPoint, u32, u32) { let chars = self - .suggestion_snapshot - .chars_at(SuggestionPoint::new(output.row(), 0)); + .editor_addition_snapshot + .chars_at(EditorAdditionPoint::new(output.row(), 0)); let expanded = output.column(); let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( - SuggestionPoint::new(output.row(), collapsed as u32), + EditorAdditionPoint::new(output.row(), collapsed as u32), expanded_char_column, to_next_stop, ) @@ -321,17 +329,35 @@ impl TabSnapshot { pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { let fold_point = self + .editor_addition_snapshot .suggestion_snapshot .fold_snapshot .to_fold_point(point, bias); - let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - self.to_tab_point(suggestion_point) + let suggestion_point = self + .editor_addition_snapshot + .suggestion_snapshot + .to_suggestion_point(fold_point); + let editor_addition_point = self + .editor_addition_snapshot + .to_editor_addition_point(suggestion_point); + self.to_tab_point(editor_addition_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { - let suggestion_point = self.to_suggestion_point(point, bias).0; - let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); - fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) + let editor_addition_point = self.to_editor_addition_point(point, bias).0; + let suggestion_point = self + .editor_addition_snapshot + .to_suggestion_point(editor_addition_point, bias); + let fold_point = self + .editor_addition_snapshot + .suggestion_snapshot + .to_fold_point(suggestion_point); + fold_point.to_buffer_point( + &self + .editor_addition_snapshot + .suggestion_snapshot + .fold_snapshot, + ) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { @@ -490,7 +516,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { const SPACES: &str = " "; pub struct TabChunks<'a> { - suggestion_chunks: SuggestionChunks<'a>, + editor_addition_chunks: EditorAdditionChunks<'a>, chunk: Chunk<'a>, column: u32, max_expansion_column: u32, @@ -506,7 +532,7 @@ impl<'a> Iterator for TabChunks<'a> { fn next(&mut self) -> Option { if self.chunk.text.is_empty() { - if let Some(chunk) = self.suggestion_chunks.next() { + if let Some(chunk) = self.editor_addition_chunks.next() { self.chunk = chunk; if self.inside_leading_tab { self.chunk.text = &self.chunk.text[1..]; @@ -574,7 +600,10 @@ impl<'a> Iterator for TabChunks<'a> { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, + display_map::{ + editor_addition_map::EditorAdditionMap, fold_map::FoldMap, + suggestion_map::SuggestionMap, + }, MultiBuffer, }; use rand::{prelude::StdRng, Rng}; @@ -585,7 +614,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); @@ -602,7 +632,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); + let (_, mut tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), output); @@ -626,15 +657,15 @@ mod tests { let input_point = Point::new(0, ix as u32); let output_point = Point::new(0, output.find(c).unwrap() as u32); assert_eq!( - tab_snapshot.to_tab_point(SuggestionPoint(input_point)), + tab_snapshot.to_tab_point(EditorAdditionPoint(input_point)), TabPoint(output_point), "to_tab_point({input_point:?})" ); assert_eq!( tab_snapshot - .to_suggestion_point(TabPoint(output_point), Bias::Left) + .to_editor_addition_point(TabPoint(output_point), Bias::Left) .0, - SuggestionPoint(input_point), + EditorAdditionPoint(input_point), "to_suggestion_point({output_point:?})" ); } @@ -650,7 +681,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); + let (_, mut tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), input); @@ -664,7 +696,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); assert_eq!( chunks(&tab_snapshot, TabPoint::zero()), @@ -728,6 +761,9 @@ mod tests { let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); + let (editor_addition_map, _) = EditorAdditionMap::new(suggestion_snapshot.clone()); + let (suggestion_snapshot, _) = editor_addition_map.randomly_mutate(&mut rng); + log::info!("EditorAdditionMap text: {:?}", suggestion_snapshot.text()); let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index dad264d57d3ec1de1f4f7ce41537e588ab16933e..2ae11b3d564f7536f94f703b05df11e8dc5c5583 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,5 @@ use super::{ - suggestion_map::SuggestionBufferRows, + editor_addition_map::EditorAdditionBufferRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, TextHighlights, }; @@ -65,7 +65,7 @@ pub struct WrapChunks<'a> { #[derive(Clone)] pub struct WrapBufferRows<'a> { - input_buffer_rows: SuggestionBufferRows<'a>, + input_buffer_rows: EditorAdditionBufferRows<'a>, input_buffer_row: Option, output_row: u32, soft_wrapped: bool, @@ -762,19 +762,29 @@ impl WrapSnapshot { let mut prev_fold_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let suggestion_point = self + let editor_addition_point = self .tab_snapshot - .to_suggestion_point(tab_point, Bias::Left) + .to_editor_addition_point(tab_point, Bias::Left) .0; + let suggestion_point = self + .tab_snapshot + .editor_addition_snapshot + .to_suggestion_point(editor_addition_point, Bias::Left); let fold_point = self .tab_snapshot + .editor_addition_snapshot .suggestion_snapshot .to_fold_point(suggestion_point); if fold_point.row() == prev_fold_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let buffer_point = fold_point - .to_buffer_point(&self.tab_snapshot.suggestion_snapshot.fold_snapshot); + let buffer_point = fold_point.to_buffer_point( + &self + .tab_snapshot + .editor_addition_snapshot + .suggestion_snapshot + .fold_snapshot, + ); expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); prev_fold_row = fold_point.row(); } @@ -1038,7 +1048,10 @@ fn consolidate_wrap_edits(edits: &mut Vec) { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap, tab_map::TabMap}, + display_map::{ + editor_addition_map::EditorAdditionMap, fold_map::FoldMap, + suggestion_map::SuggestionMap, tab_map::TabMap, + }, MultiBuffer, }; use gpui::test::observe; @@ -1093,7 +1106,13 @@ mod tests { log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (editor_addition_map, editors_additions_snapshot) = + EditorAdditionMap::new(suggestion_snapshot.clone()); + log::info!( + "EditorAdditionsMap text: {:?}", + editors_additions_snapshot.text() + ); + let (tab_map, _) = TabMap::new(editors_additions_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); @@ -1141,8 +1160,10 @@ mod tests { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1153,8 +1174,10 @@ mod tests { 40..=59 => { let (suggestion_snapshot, suggestion_edits) = suggestion_map.randomly_mutate(&mut rng); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1178,8 +1201,10 @@ mod tests { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); + let (editor_addition_snapshot, editor_addition_edits) = + editor_addition_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); @@ -1227,7 +1252,7 @@ mod tests { if tab_size.get() == 1 || !wrapped_snapshot .tab_snapshot - .suggestion_snapshot + .editor_addition_snapshot .text() .contains('\t') { From 4c3c0eb796d478dea50156e2f295a7e56333e5c7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 6 Jun 2023 15:16:46 +0300 Subject: [PATCH 050/169] Draft the hint render data flow --- crates/editor/src/display_map.rs | 21 ++++++++++ .../src/display_map/editor_addition_map.rs | 42 +++++++++++++++---- crates/editor/src/display_map/tab_map.rs | 12 +++--- crates/editor/src/editor.rs | 25 +++++++++-- crates/project/src/lsp_command.rs | 2 + crates/project/src/project.rs | 25 +++++++---- 6 files changed, 103 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5dad501df6731532bd713c8a4ae22eccfbda03f6..a870b70f7b1e04b654a1a515849166a4d3456bdb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -30,6 +30,8 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; +use self::editor_addition_map::InlayHintToRender; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -286,6 +288,25 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } + pub fn set_inlay_hints(&self, new_hints: &[project::InlayHint], cx: &mut ModelContext) { + let multi_buffer = self.buffer.read(cx); + self.editor_addition_map.set_inlay_hints( + new_hints + .into_iter() + .filter_map(|hint| { + let buffer = multi_buffer.buffer(hint.buffer_id)?.read(cx); + let snapshot = buffer.snapshot(); + Some(InlayHintToRender { + position: editor_addition_map::EditorAdditionPoint( + text::ToPoint::to_point(&hint.position, &snapshot), + ), + text: hint.text().trim_end().into(), + }) + }) + .collect(), + ) + } + fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { let language = buffer .read(cx) diff --git a/crates/editor/src/display_map/editor_addition_map.rs b/crates/editor/src/display_map/editor_addition_map.rs index 1388f91d306351fc613322dec135c05d6a573cef..3603400efc985968b7ea3b10e530fca6bfd01b7b 100644 --- a/crates/editor/src/display_map/editor_addition_map.rs +++ b/crates/editor/src/display_map/editor_addition_map.rs @@ -10,17 +10,20 @@ use super::{ TextHighlights, }; use gpui::fonts::HighlightStyle; -use language::{Chunk, Edit, Point, TextSummary}; +use language::{Chunk, Edit, Point, Rope, TextSummary}; +use parking_lot::Mutex; +use project::InlayHint; use rand::Rng; use sum_tree::Bias; -pub struct EditorAdditionMap; +pub struct EditorAdditionMap(Mutex); #[derive(Clone)] pub struct EditorAdditionSnapshot { // TODO kb merge these two together pub suggestion_snapshot: SuggestionSnapshot, pub version: usize, + hints: Vec, } pub type EditorAdditionEdit = Edit; @@ -63,6 +66,12 @@ pub struct EditorAdditionChunks<'a> { _z: &'a std::marker::PhantomData<()>, } +#[derive(Clone)] +pub struct InlayHintToRender { + pub(super) position: EditorAdditionPoint, + pub(super) text: Rope, +} + impl<'a> Iterator for EditorAdditionChunks<'a> { type Item = Chunk<'a>; @@ -95,7 +104,12 @@ impl EditorAdditionPoint { impl EditorAdditionMap { pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, EditorAdditionSnapshot) { - todo!("TODO kb") + let snapshot = EditorAdditionSnapshot { + suggestion_snapshot: suggestion_snapshot.clone(), + version: 0, + hints: Vec::new(), + }; + (Self(Mutex::new(snapshot.clone())), snapshot) } pub fn sync( @@ -103,14 +117,24 @@ impl EditorAdditionMap { suggestion_snapshot: SuggestionSnapshot, suggestion_edits: Vec, ) -> (EditorAdditionSnapshot, Vec) { - todo!("TODO kb") + let mut snapshot = self.0.lock(); + + if snapshot.suggestion_snapshot.version != suggestion_snapshot.version { + snapshot.version += 1; + } + + let editor_addition_edits = Vec::new(); + { + todo!("TODO kb") + } + + snapshot.suggestion_snapshot = suggestion_snapshot; + + (snapshot.clone(), editor_addition_edits) } - pub fn randomly_mutate( - &self, - rng: &mut impl Rng, - ) -> (EditorAdditionSnapshot, Vec) { - todo!("TODO kb") + pub fn set_inlay_hints(&self, new_hints: Vec) { + self.0.lock().hints = new_hints; } } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index b54de1ad651538d629bfc94ba03615268ea74339..a3e8cbdb373aff202d8b5f80b67ff873bbd8fdd6 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -761,11 +761,13 @@ mod tests { let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (editor_addition_map, _) = EditorAdditionMap::new(suggestion_snapshot.clone()); - let (suggestion_snapshot, _) = editor_addition_map.randomly_mutate(&mut rng); - log::info!("EditorAdditionMap text: {:?}", suggestion_snapshot.text()); + let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot.clone()); + log::info!( + "EditorAdditionMap text: {:?}", + editor_addition_snapshot.text() + ); - let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(editor_addition_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); let text = text::Rope::from(tabs_snapshot.text().as_str()); @@ -803,7 +805,7 @@ mod tests { ); let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); - if tab_size.get() > 1 && suggestion_snapshot.text().contains('\t') { + if tab_size.get() > 1 && editor_addition_snapshot.text().contains('\t') { actual_summary.longest_row = expected_summary.longest_row; actual_summary.longest_row_chars = expected_summary.longest_row_chars; } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 63b3c4f6a3b19d7b7b6a743b45db78ce431bf52f..bfdfbd2a77ef56c2dc704eadfcc25a857a9eef51 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1169,11 +1169,19 @@ impl InlayHintState { Self::first_timestamp_newer(timestamp, ¤t_timestamp) } - fn update_if_newer(&self, new_hints: Vec, new_timestamp: HashMap) { + fn update_if_newer( + &self, + new_hints: Vec, + new_timestamp: HashMap, + ) -> bool { let mut guard = self.0.write(); if Self::first_timestamp_newer(&new_timestamp, &guard.0) { guard.0 = new_timestamp; guard.1 = new_hints; + + true + } else { + false } } @@ -2688,7 +2696,7 @@ impl Editor { let inlay_hints_storage = Arc::clone(&self.inlay_hints); if inlay_hints_storage.is_newer(&new_timestamp) { - cx.spawn(|_, _| async move { + cx.spawn(|editor, mut cx| async move { let mut new_hints = Vec::new(); for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { @@ -2696,7 +2704,18 @@ impl Editor { Err(e) => error!("Failed to update hints for buffer: {e:#}"), } } - inlay_hints_storage.update_if_newer(new_hints, new_timestamp); + + // TODO kb another odd clone, can be avoid all this? hide hints behind a handle? + if inlay_hints_storage.update_if_newer(new_hints.clone(), new_timestamp) { + editor + .update(&mut cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.set_inlay_hints(&new_hints, cx) + }); + }) + .log_err() + .unwrap_or(()) + } }) .detach(); } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index fb01becaf417c917da91251a19c561a8aec5462f..e735773f4b244a1603ed61ac7aa3691eadd35bc8 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1834,6 +1834,7 @@ impl LspCommand for InlayHints { .unwrap_or_default() .into_iter() .map(|lsp_hint| InlayHint { + buffer_id: buffer.id() as u64, position: origin_buffer.anchor_after( origin_buffer .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), @@ -2006,6 +2007,7 @@ impl LspCommand for InlayHints { let mut hints = Vec::new(); for message_hint in message.hints { let hint = InlayHint { + buffer_id: buffer.id() as u64, position: message_hint .position .and_then(language::proto::deserialize_anchor) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e941eee0329f753d5f9428507b370538b1cdcab6..c33b563ea5516caf7fbf883aacd801267114f207 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -29,6 +29,7 @@ use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, }; +use itertools::Itertools; use language::{ language_settings::{language_settings, FormatOnSave, Formatter}, point_to_lsp, @@ -320,46 +321,56 @@ pub struct DiagnosticSummary { pub warning_count: usize, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Location { pub buffer: ModelHandle, pub range: Range, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHint { + pub buffer_id: u64, pub position: Anchor, pub label: InlayHintLabel, pub kind: Option, pub tooltip: Option, } -#[derive(Debug, Clone)] +impl InlayHint { + pub fn text(&self) -> String { + match &self.label { + InlayHintLabel::String(s) => s.to_owned(), + InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum InlayHintLabel { String(String), LabelParts(Vec), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, pub location: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum InlayHintTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum InlayHintLabelPartTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MarkupContent { pub kind: String, pub value: String, From 83f4320b60f5335a9c21acdb99dc93c89260631e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 6 Jun 2023 15:51:54 +0300 Subject: [PATCH 051/169] Replace todo!s with stub calls to make Zed work --- .../src/display_map/editor_addition_map.rs | 87 ++++++++++++++----- 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/display_map/editor_addition_map.rs b/crates/editor/src/display_map/editor_addition_map.rs index 3603400efc985968b7ea3b10e530fca6bfd01b7b..63c65e54056801b392e276664112d1d0aad842be 100644 --- a/crates/editor/src/display_map/editor_addition_map.rs +++ b/crates/editor/src/display_map/editor_addition_map.rs @@ -6,7 +6,10 @@ use std::ops::{Add, AddAssign, Range, Sub}; use crate::MultiBufferSnapshot; use super::{ - suggestion_map::{SuggestionEdit, SuggestionPoint, SuggestionSnapshot}, + suggestion_map::{ + SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint, + SuggestionSnapshot, + }, TextHighlights, }; use gpui::fonts::HighlightStyle; @@ -58,12 +61,11 @@ pub struct EditorAdditionPoint(pub Point); #[derive(Clone)] pub struct EditorAdditionBufferRows<'a> { - _z: &'a std::marker::PhantomData<()>, + suggestion_rows: SuggestionBufferRows<'a>, } -#[derive(Clone)] pub struct EditorAdditionChunks<'a> { - _z: &'a std::marker::PhantomData<()>, + suggestion_chunks: SuggestionChunks<'a>, } #[derive(Clone)] @@ -76,7 +78,7 @@ impl<'a> Iterator for EditorAdditionChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { - todo!("TODO kb") + self.suggestion_chunks.next() } } @@ -84,7 +86,7 @@ impl<'a> Iterator for EditorAdditionBufferRows<'a> { type Item = Option; fn next(&mut self) -> Option { - todo!("TODO kb") + self.suggestion_rows.next() } } @@ -123,9 +125,15 @@ impl EditorAdditionMap { snapshot.version += 1; } - let editor_addition_edits = Vec::new(); - { - todo!("TODO kb") + let mut editor_addition_edits = Vec::new(); + for suggestion_edit in suggestion_edits { + let old = suggestion_edit.old; + let new = suggestion_edit.new; + // TODO kb copied from suggestion_map + editor_addition_edits.push(EditorAdditionEdit { + old: EditorAdditionOffset(old.start.0)..EditorAdditionOffset(old.end.0), + new: EditorAdditionOffset(old.start.0)..EditorAdditionOffset(new.end.0), + }) } snapshot.suggestion_snapshot = suggestion_snapshot; @@ -140,47 +148,71 @@ impl EditorAdditionMap { impl EditorAdditionSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.suggestion_snapshot.buffer_snapshot() } pub fn to_point(&self, offset: EditorAdditionOffset) -> EditorAdditionPoint { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.to_editor_addition_point( + self.suggestion_snapshot + .to_point(super::suggestion_map::SuggestionOffset(offset.0)), + ) } pub fn max_point(&self) -> EditorAdditionPoint { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.to_editor_addition_point(self.suggestion_snapshot.max_point()) } pub fn to_offset(&self, point: EditorAdditionPoint) -> EditorAdditionOffset { - todo!("TODO kb") + // TODO kb copied from suggestion_map + EditorAdditionOffset( + self.suggestion_snapshot + .to_offset(self.to_suggestion_point(point, Bias::Left)) + .0, + ) } pub fn chars_at(&self, start: EditorAdditionPoint) -> impl '_ + Iterator { - Vec::new().into_iter() + self.suggestion_snapshot + .chars_at(self.to_suggestion_point(start, Bias::Left)) } - pub fn to_suggestion_point(&self, point: EditorAdditionPoint, bias: Bias) -> SuggestionPoint { - todo!("TODO kb") + // TODO kb what to do with bias? + pub fn to_suggestion_point(&self, point: EditorAdditionPoint, _: Bias) -> SuggestionPoint { + SuggestionPoint(point.0) } pub fn to_editor_addition_point(&self, point: SuggestionPoint) -> EditorAdditionPoint { - todo!("TODO kb") + EditorAdditionPoint(point.0) } pub fn clip_point(&self, point: EditorAdditionPoint, bias: Bias) -> EditorAdditionPoint { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.to_editor_addition_point( + self.suggestion_snapshot + .clip_point(self.to_suggestion_point(point, bias), bias), + ) } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.suggestion_snapshot.text_summary_for_range( + self.to_suggestion_point(range.start, Bias::Left) + ..self.to_suggestion_point(range.end, Bias::Left), + ) } pub fn buffer_rows<'a>(&'a self, row: u32) -> EditorAdditionBufferRows<'a> { - todo!("TODO kb") + EditorAdditionBufferRows { + suggestion_rows: self.suggestion_snapshot.buffer_rows(row), + } } pub fn line_len(&self, row: u32) -> u32 { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.suggestion_snapshot.line_len(row) } pub fn chunks<'a>( @@ -190,11 +222,20 @@ impl EditorAdditionSnapshot { text_highlights: Option<&'a TextHighlights>, suggestion_highlight: Option, ) -> EditorAdditionChunks<'a> { - todo!("TODO kb") + // TODO kb copied from suggestion_map + EditorAdditionChunks { + suggestion_chunks: self.suggestion_snapshot.chunks( + SuggestionOffset(range.start.0)..SuggestionOffset(range.end.0), + language_aware, + text_highlights, + suggestion_highlight, + ), + } } #[cfg(test)] pub fn text(&self) -> String { - todo!("TODO kb") + // TODO kb copied from suggestion_map + self.suggestion_snapshot.text() } } From 78b3c9b88a779d869d58d0923dc36e0aee613db8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 6 Jun 2023 23:52:10 +0300 Subject: [PATCH 052/169] Store hints in the new map only --- crates/editor/src/editor.rs | 39 ++++++++++++------------------------ crates/editor/src/element.rs | 5 ----- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bfdfbd2a77ef56c2dc704eadfcc25a857a9eef51..3cc33ac03fdac730206ba22169cf13aca7346831 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -72,9 +72,7 @@ pub use multi_buffer::{ use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; use parking_lot::RwLock; -use project::{ - FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction, -}; +use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -539,7 +537,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_hints: Arc, + inlay_hints_version: InlayHintVersion, _subscriptions: Vec, } @@ -1156,29 +1154,19 @@ impl CopilotState { } } -#[derive(Debug, Default)] -struct InlayHintState(RwLock<(HashMap, Vec)>); - -impl InlayHintState { - fn read(&self) -> Vec { - self.0.read().1.clone() - } +#[derive(Debug, Default, Clone)] +struct InlayHintVersion(Arc>>); +impl InlayHintVersion { fn is_newer(&self, timestamp: &HashMap) -> bool { - let current_timestamp = self.0.read().0.clone(); + let current_timestamp = self.0.read(); Self::first_timestamp_newer(timestamp, ¤t_timestamp) } - fn update_if_newer( - &self, - new_hints: Vec, - new_timestamp: HashMap, - ) -> bool { + fn update_if_newer(&self, new_timestamp: HashMap) -> bool { let mut guard = self.0.write(); - if Self::first_timestamp_newer(&new_timestamp, &guard.0) { - guard.0 = new_timestamp; - guard.1 = new_hints; - + if Self::first_timestamp_newer(&new_timestamp, &guard) { + *guard = new_timestamp; true } else { false @@ -1414,7 +1402,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hints: Arc::new(InlayHintState::default()), + inlay_hints_version: InlayHintVersion::default(), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2694,8 +2682,8 @@ impl Editor { }, ); - let inlay_hints_storage = Arc::clone(&self.inlay_hints); - if inlay_hints_storage.is_newer(&new_timestamp) { + let current_hints_version = self.inlay_hints_version.clone(); + if current_hints_version.is_newer(&new_timestamp) { cx.spawn(|editor, mut cx| async move { let mut new_hints = Vec::new(); for task_result in futures::future::join_all(hint_fetch_tasks).await { @@ -2705,8 +2693,7 @@ impl Editor { } } - // TODO kb another odd clone, can be avoid all this? hide hints behind a handle? - if inlay_hints_storage.update_if_newer(new_hints.clone(), new_timestamp) { + if current_hints_version.update_if_newer(new_timestamp) { editor .update(&mut cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2cf9ff5fe66d327846bfcc1142856720f1e4b60b..6525e7fc222843fdfa04a94317f4a2a95f6dadcf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -879,7 +879,6 @@ impl EditorElement { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { let row = start_row + ix as u32; line_with_invisibles.draw( - editor, layout, row, scroll_top, @@ -1795,7 +1794,6 @@ impl LineWithInvisibles { fn draw( &self, - editor: &mut Editor, layout: &LayoutState, row: u32, scroll_top: f32, @@ -1819,9 +1817,6 @@ impl LineWithInvisibles { cx, ); - // TODO kb bad: syscalls + cloning happen very frequently, check the timestamp first - let new_hints = editor.inlay_hints.read(); - self.draw_invisibles( &selection_ranges, layout, From 92876345482861b794b9797c3babd101cc74fa05 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 7 Jun 2023 18:59:39 +0300 Subject: [PATCH 053/169] Prepare to find diffs between inlay hint generations --- crates/editor/src/display_map.rs | 19 ++++- .../src/display_map/editor_addition_map.rs | 78 ++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a870b70f7b1e04b654a1a515849166a4d3456bdb..e34d919164a4d860471e73fe415b429876bba11d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -288,13 +288,28 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn set_inlay_hints(&self, new_hints: &[project::InlayHint], cx: &mut ModelContext) { + pub fn set_inlay_hints( + &mut self, + new_hints: &[project::InlayHint], + cx: &mut ModelContext, + ) { + dbg!("---", new_hints.len()); let multi_buffer = self.buffer.read(cx); + + let zz = dbg!(multi_buffer + .all_buffers() + .into_iter() + .map(|buffer_handle| buffer_handle.id()) + .collect::>()); + self.editor_addition_map.set_inlay_hints( new_hints .into_iter() .filter_map(|hint| { - let buffer = multi_buffer.buffer(hint.buffer_id)?.read(cx); + // TODO kb this is all wrong, need to manage both(?) or at least the remote buffer id when needed. + // Here though, `.buffer(` requires remote buffer id, so use the workaround above. + dbg!(zz.contains(&(hint.buffer_id as usize))); + let buffer = dbg!(multi_buffer.buffer(dbg!(hint.buffer_id)))?.read(cx); let snapshot = buffer.snapshot(); Some(InlayHintToRender { position: editor_addition_map::EditorAdditionPoint( diff --git a/crates/editor/src/display_map/editor_addition_map.rs b/crates/editor/src/display_map/editor_addition_map.rs index 63c65e54056801b392e276664112d1d0aad842be..4f88610f44b778bc5012757a8a7dcf063a52aa55 100644 --- a/crates/editor/src/display_map/editor_addition_map.rs +++ b/crates/editor/src/display_map/editor_addition_map.rs @@ -1,7 +1,10 @@ #![allow(unused)] // TODO kb -use std::ops::{Add, AddAssign, Range, Sub}; +use std::{ + ops::{Add, AddAssign, Range, Sub}, + sync::atomic::{self, AtomicUsize}, +}; use crate::MultiBufferSnapshot; @@ -12,21 +15,43 @@ use super::{ }, TextHighlights, }; +use collections::HashMap; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; use project::InlayHint; use rand::Rng; -use sum_tree::Bias; +use sum_tree::{Bias, SumTree}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InlayHintId(usize); -pub struct EditorAdditionMap(Mutex); +pub struct EditorAdditionMap { + snapshot: Mutex, + next_hint_id: AtomicUsize, + hints: HashMap, +} #[derive(Clone)] pub struct EditorAdditionSnapshot { // TODO kb merge these two together pub suggestion_snapshot: SuggestionSnapshot, + transforms: SumTree, pub version: usize, - hints: Vec, +} + +#[derive(Clone)] +struct Transform { + input: TextSummary, + output: TextSummary, +} + +impl sum_tree::Item for Transform { + type Summary = TextSummary; + + fn summary(&self) -> Self::Summary { + self.output.clone() + } } pub type EditorAdditionEdit = Edit; @@ -68,7 +93,7 @@ pub struct EditorAdditionChunks<'a> { suggestion_chunks: SuggestionChunks<'a>, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct InlayHintToRender { pub(super) position: EditorAdditionPoint, pub(super) text: Rope, @@ -109,9 +134,17 @@ impl EditorAdditionMap { let snapshot = EditorAdditionSnapshot { suggestion_snapshot: suggestion_snapshot.clone(), version: 0, - hints: Vec::new(), + transforms: SumTree::new(), }; - (Self(Mutex::new(snapshot.clone())), snapshot) + + ( + Self { + snapshot: Mutex::new(snapshot.clone()), + next_hint_id: AtomicUsize::new(0), + hints: HashMap::default(), + }, + snapshot, + ) } pub fn sync( @@ -119,13 +152,15 @@ impl EditorAdditionMap { suggestion_snapshot: SuggestionSnapshot, suggestion_edits: Vec, ) -> (EditorAdditionSnapshot, Vec) { - let mut snapshot = self.0.lock(); + let mut snapshot = self.snapshot.lock(); if snapshot.suggestion_snapshot.version != suggestion_snapshot.version { snapshot.version += 1; } let mut editor_addition_edits = Vec::new(); + + dbg!(&suggestion_edits); for suggestion_edit in suggestion_edits { let old = suggestion_edit.old; let new = suggestion_edit.new; @@ -141,8 +176,31 @@ impl EditorAdditionMap { (snapshot.clone(), editor_addition_edits) } - pub fn set_inlay_hints(&self, new_hints: Vec) { - self.0.lock().hints = new_hints; + // TODO kb replace set_inlay_hints with this + pub fn splice( + &mut self, + to_remove: Vec, + to_insert: Vec, + ) -> Vec { + // Order removals and insertions by position. + // let anchors; + + // Remove and insert inlays in a single traversal across the tree. + todo!("TODO kb") + } + + pub fn set_inlay_hints(&mut self, new_hints: Vec) { + dbg!(new_hints.len()); + // TODO kb reuse ids for hints that did not change and similar things + self.hints = new_hints + .into_iter() + .map(|hint| { + ( + InlayHintId(self.next_hint_id.fetch_add(1, atomic::Ordering::SeqCst)), + hint, + ) + }) + .collect(); } } From b5233b3ad5a8886b3d48675ee95593c91a9afbd3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 7 Jun 2023 23:29:27 +0300 Subject: [PATCH 054/169] Rename the new map --- crates/editor/src/display_map.rs | 67 ++++---- crates/editor/src/display_map/block_map.rs | 45 +++-- .../{editor_addition_map.rs => inlay_map.rs} | 86 +++++----- crates/editor/src/display_map/tab_map.rs | 154 ++++++++---------- crates/editor/src/display_map/wrap_map.rs | 49 +++--- 5 files changed, 190 insertions(+), 211 deletions(-) rename crates/editor/src/display_map/{editor_addition_map.rs => inlay_map.rs} (73%) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e34d919164a4d860471e73fe415b429876bba11d..ade36990dea5700b19c167fce2e9176e8b7d424e 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,6 +1,6 @@ mod block_map; -mod editor_addition_map; mod fold_map; +mod inlay_map; mod suggestion_map; mod tab_map; mod wrap_map; @@ -8,13 +8,13 @@ mod wrap_map; use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; -use editor_addition_map::EditorAdditionMap; use fold_map::{FoldMap, FoldOffset}; use gpui::{ color::Color, fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; +use inlay_map::InlayMap; use language::{ language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, }; @@ -30,7 +30,7 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; -use self::editor_addition_map::InlayHintToRender; +use self::inlay_map::InlayHintToRender; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { @@ -49,7 +49,7 @@ pub struct DisplayMap { buffer_subscription: BufferSubscription, fold_map: FoldMap, suggestion_map: SuggestionMap, - editor_addition_map: EditorAdditionMap, + inlay_map: InlayMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, @@ -76,7 +76,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&buffer, cx); let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); let (suggestion_map, snapshot) = SuggestionMap::new(snapshot); - let (editor_addition_map, snapshot) = EditorAdditionMap::new(snapshot); + let (inlay_map, snapshot) = InlayMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); @@ -86,7 +86,7 @@ impl DisplayMap { buffer_subscription, fold_map, suggestion_map, - editor_addition_map, + inlay_map, tab_map, wrap_map, block_map, @@ -100,13 +100,13 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); - let (editor_addition_snapshot, edits) = self - .editor_addition_map + let (inlay_snapshot, edits) = self + .inlay_map .sync(suggestion_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); let (tab_snapshot, edits) = self.tab_map - .sync(editor_addition_snapshot.clone(), edits, tab_size); + .sync(inlay_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); @@ -116,7 +116,7 @@ impl DisplayMap { buffer_snapshot: self.buffer.read(cx).snapshot(cx), fold_snapshot, suggestion_snapshot, - editor_addition_snapshot, + inlay_snapshot, tab_snapshot, wrap_snapshot, block_snapshot, @@ -144,7 +144,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -152,7 +152,7 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -171,7 +171,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -179,7 +179,7 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -197,7 +197,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -216,7 +216,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -265,7 +265,7 @@ impl DisplayMap { let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits, old_suggestion) = self.suggestion_map.replace(new_suggestion, snapshot, edits); - let (snapshot, edits) = self.editor_addition_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -302,7 +302,7 @@ impl DisplayMap { .map(|buffer_handle| buffer_handle.id()) .collect::>()); - self.editor_addition_map.set_inlay_hints( + self.inlay_map.set_inlay_hints( new_hints .into_iter() .filter_map(|hint| { @@ -312,9 +312,10 @@ impl DisplayMap { let buffer = dbg!(multi_buffer.buffer(dbg!(hint.buffer_id)))?.read(cx); let snapshot = buffer.snapshot(); Some(InlayHintToRender { - position: editor_addition_map::EditorAdditionPoint( - text::ToPoint::to_point(&hint.position, &snapshot), - ), + position: inlay_map::InlayPoint(text::ToPoint::to_point( + &hint.position, + &snapshot, + )), text: hint.text().trim_end().into(), }) }) @@ -340,7 +341,7 @@ pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, fold_snapshot: fold_map::FoldSnapshot, suggestion_snapshot: suggestion_map::SuggestionSnapshot, - editor_addition_snapshot: editor_addition_map::EditorAdditionSnapshot, + inlay_snapshot: inlay_map::InlaySnapshot, tab_snapshot: tab_map::TabSnapshot, wrap_snapshot: wrap_map::WrapSnapshot, block_snapshot: block_map::BlockSnapshot, @@ -418,10 +419,10 @@ impl DisplaySnapshot { fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { let fold_point = self.fold_snapshot.to_fold_point(point, bias); let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - let editor_addition_point = self - .editor_addition_snapshot - .to_editor_addition_point(suggestion_point); - let tab_point = self.tab_snapshot.to_tab_point(editor_addition_point); + let inlay_point = self + .inlay_snapshot + .to_inlay_point(suggestion_point); + let tab_point = self.tab_snapshot.to_tab_point(inlay_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); DisplayPoint(block_point) @@ -431,13 +432,13 @@ impl DisplaySnapshot { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); - let editor_addition_point = self + let inlay_point = self .tab_snapshot - .to_editor_addition_point(tab_point, bias) + .to_inlay_point(tab_point, bias) .0; let suggestion_point = self - .editor_addition_snapshot - .to_suggestion_point(editor_addition_point, bias); + .inlay_snapshot + .to_suggestion_point(inlay_point, bias); let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_point(&self.fold_snapshot) } @@ -851,10 +852,10 @@ impl DisplayPoint { pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); - let editor_addition_point = map.tab_snapshot.to_editor_addition_point(tab_point, bias).0; + let inlay_point = map.tab_snapshot.to_inlay_point(tab_point, bias).0; let suggestion_point = map - .editor_addition_snapshot - .to_suggestion_point(editor_addition_point, bias); + .inlay_snapshot + .to_suggestion_point(inlay_point, bias); let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_offset(&map.fold_snapshot) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index a01048670592959125c95632af21d931e2758ea8..e37c36f5ac29c10efe6b2652085f16701081d278 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -989,7 +989,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { #[cfg(test)] mod tests { use super::*; - use crate::display_map::editor_addition_map::EditorAdditionMap; + use crate::display_map::inlay_map::InlayMap; use crate::display_map::suggestion_map::SuggestionMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::multi_buffer::MultiBuffer; @@ -1033,9 +1033,8 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (editor_addition_map, editor_addition_snapshot) = - EditorAdditionMap::new(suggestion_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(editor_addition_snapshot, 1.try_into().unwrap()); + let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1182,13 +1181,10 @@ mod tests { fold_map.read(buffer_snapshot, subscription.consume().into_inner()); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); - let (tab_snapshot, tab_edits) = tab_map.sync( - editor_addition_snapshot, - editor_addition_edits, - 4.try_into().unwrap(), - ); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (tab_snapshot, tab_edits) = + tab_map.sync(inlay_snapshot, inlay_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1215,8 +1211,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); - let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1288,9 +1284,8 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (editor_addition_map, editor_addition_snapshot) = - EditorAdditionMap::new(suggestion_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( @@ -1347,10 +1342,10 @@ mod tests { fold_map.read(buffer_snapshot.clone(), vec![]); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1374,10 +1369,10 @@ mod tests { fold_map.read(buffer_snapshot.clone(), vec![]); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1399,10 +1394,10 @@ mod tests { let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); diff --git a/crates/editor/src/display_map/editor_addition_map.rs b/crates/editor/src/display_map/inlay_map.rs similarity index 73% rename from crates/editor/src/display_map/editor_addition_map.rs rename to crates/editor/src/display_map/inlay_map.rs index 4f88610f44b778bc5012757a8a7dcf063a52aa55..18ac59fceff2ece8f55e0cb9b4ec2d39dd8d3713 100644 --- a/crates/editor/src/display_map/editor_addition_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -26,14 +26,14 @@ use sum_tree::{Bias, SumTree}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InlayHintId(usize); -pub struct EditorAdditionMap { - snapshot: Mutex, +pub struct InlayMap { + snapshot: Mutex, next_hint_id: AtomicUsize, hints: HashMap, } #[derive(Clone)] -pub struct EditorAdditionSnapshot { +pub struct InlaySnapshot { // TODO kb merge these two together pub suggestion_snapshot: SuggestionSnapshot, transforms: SumTree, @@ -54,12 +54,12 @@ impl sum_tree::Item for Transform { } } -pub type EditorAdditionEdit = Edit; +pub type InlayEdit = Edit; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct EditorAdditionOffset(pub usize); +pub struct InlayOffset(pub usize); -impl Add for EditorAdditionOffset { +impl Add for InlayOffset { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -67,7 +67,7 @@ impl Add for EditorAdditionOffset { } } -impl Sub for EditorAdditionOffset { +impl Sub for InlayOffset { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { @@ -75,31 +75,31 @@ impl Sub for EditorAdditionOffset { } } -impl AddAssign for EditorAdditionOffset { +impl AddAssign for InlayOffset { fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; } } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct EditorAdditionPoint(pub Point); +pub struct InlayPoint(pub Point); #[derive(Clone)] -pub struct EditorAdditionBufferRows<'a> { +pub struct InlayBufferRows<'a> { suggestion_rows: SuggestionBufferRows<'a>, } -pub struct EditorAdditionChunks<'a> { +pub struct InlayChunks<'a> { suggestion_chunks: SuggestionChunks<'a>, } #[derive(Debug, Clone)] pub struct InlayHintToRender { - pub(super) position: EditorAdditionPoint, + pub(super) position: InlayPoint, pub(super) text: Rope, } -impl<'a> Iterator for EditorAdditionChunks<'a> { +impl<'a> Iterator for InlayChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { @@ -107,7 +107,7 @@ impl<'a> Iterator for EditorAdditionChunks<'a> { } } -impl<'a> Iterator for EditorAdditionBufferRows<'a> { +impl<'a> Iterator for InlayBufferRows<'a> { type Item = Option; fn next(&mut self) -> Option { @@ -115,7 +115,7 @@ impl<'a> Iterator for EditorAdditionBufferRows<'a> { } } -impl EditorAdditionPoint { +impl InlayPoint { pub fn new(row: u32, column: u32) -> Self { Self(Point::new(row, column)) } @@ -129,9 +129,9 @@ impl EditorAdditionPoint { } } -impl EditorAdditionMap { - pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, EditorAdditionSnapshot) { - let snapshot = EditorAdditionSnapshot { +impl InlayMap { + pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) { + let snapshot = InlaySnapshot { suggestion_snapshot: suggestion_snapshot.clone(), version: 0, transforms: SumTree::new(), @@ -151,29 +151,29 @@ impl EditorAdditionMap { &self, suggestion_snapshot: SuggestionSnapshot, suggestion_edits: Vec, - ) -> (EditorAdditionSnapshot, Vec) { + ) -> (InlaySnapshot, Vec) { let mut snapshot = self.snapshot.lock(); if snapshot.suggestion_snapshot.version != suggestion_snapshot.version { snapshot.version += 1; } - let mut editor_addition_edits = Vec::new(); + let mut inlay_edits = Vec::new(); dbg!(&suggestion_edits); for suggestion_edit in suggestion_edits { let old = suggestion_edit.old; let new = suggestion_edit.new; // TODO kb copied from suggestion_map - editor_addition_edits.push(EditorAdditionEdit { - old: EditorAdditionOffset(old.start.0)..EditorAdditionOffset(old.end.0), - new: EditorAdditionOffset(old.start.0)..EditorAdditionOffset(new.end.0), + inlay_edits.push(InlayEdit { + old: InlayOffset(old.start.0)..InlayOffset(old.end.0), + new: InlayOffset(old.start.0)..InlayOffset(new.end.0), }) } snapshot.suggestion_snapshot = suggestion_snapshot; - (snapshot.clone(), editor_addition_edits) + (snapshot.clone(), inlay_edits) } // TODO kb replace set_inlay_hints with this @@ -204,57 +204,57 @@ impl EditorAdditionMap { } } -impl EditorAdditionSnapshot { +impl InlaySnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { // TODO kb copied from suggestion_map self.suggestion_snapshot.buffer_snapshot() } - pub fn to_point(&self, offset: EditorAdditionOffset) -> EditorAdditionPoint { + pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { // TODO kb copied from suggestion_map - self.to_editor_addition_point( + self.to_inlay_point( self.suggestion_snapshot .to_point(super::suggestion_map::SuggestionOffset(offset.0)), ) } - pub fn max_point(&self) -> EditorAdditionPoint { + pub fn max_point(&self) -> InlayPoint { // TODO kb copied from suggestion_map - self.to_editor_addition_point(self.suggestion_snapshot.max_point()) + self.to_inlay_point(self.suggestion_snapshot.max_point()) } - pub fn to_offset(&self, point: EditorAdditionPoint) -> EditorAdditionOffset { + pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { // TODO kb copied from suggestion_map - EditorAdditionOffset( + InlayOffset( self.suggestion_snapshot .to_offset(self.to_suggestion_point(point, Bias::Left)) .0, ) } - pub fn chars_at(&self, start: EditorAdditionPoint) -> impl '_ + Iterator { + pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator { self.suggestion_snapshot .chars_at(self.to_suggestion_point(start, Bias::Left)) } // TODO kb what to do with bias? - pub fn to_suggestion_point(&self, point: EditorAdditionPoint, _: Bias) -> SuggestionPoint { + pub fn to_suggestion_point(&self, point: InlayPoint, _: Bias) -> SuggestionPoint { SuggestionPoint(point.0) } - pub fn to_editor_addition_point(&self, point: SuggestionPoint) -> EditorAdditionPoint { - EditorAdditionPoint(point.0) + pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { + InlayPoint(point.0) } - pub fn clip_point(&self, point: EditorAdditionPoint, bias: Bias) -> EditorAdditionPoint { + pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { // TODO kb copied from suggestion_map - self.to_editor_addition_point( + self.to_inlay_point( self.suggestion_snapshot .clip_point(self.to_suggestion_point(point, bias), bias), ) } - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { // TODO kb copied from suggestion_map self.suggestion_snapshot.text_summary_for_range( self.to_suggestion_point(range.start, Bias::Left) @@ -262,8 +262,8 @@ impl EditorAdditionSnapshot { ) } - pub fn buffer_rows<'a>(&'a self, row: u32) -> EditorAdditionBufferRows<'a> { - EditorAdditionBufferRows { + pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { + InlayBufferRows { suggestion_rows: self.suggestion_snapshot.buffer_rows(row), } } @@ -275,13 +275,13 @@ impl EditorAdditionSnapshot { pub fn chunks<'a>( &'a self, - range: Range, + range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, suggestion_highlight: Option, - ) -> EditorAdditionChunks<'a> { + ) -> InlayChunks<'a> { // TODO kb copied from suggestion_map - EditorAdditionChunks { + InlayChunks { suggestion_chunks: self.suggestion_snapshot.chunks( SuggestionOffset(range.start.0)..SuggestionOffset(range.end.0), language_aware, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index a3e8cbdb373aff202d8b5f80b67ff873bbd8fdd6..4716d625802444985ad347b1a81f1120cbc91f00 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,7 +1,5 @@ use super::{ - editor_addition_map::{ - self, EditorAdditionChunks, EditorAdditionEdit, EditorAdditionPoint, EditorAdditionSnapshot, - }, + inlay_map::{self, InlayChunks, InlayEdit, InlayPoint, InlaySnapshot}, TextHighlights, }; use crate::MultiBufferSnapshot; @@ -16,9 +14,9 @@ const MAX_EXPANSION_COLUMN: u32 = 256; pub struct TabMap(Mutex); impl TabMap { - pub fn new(input: EditorAdditionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + pub fn new(input: InlaySnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { let snapshot = TabSnapshot { - editor_addition_snapshot: input, + inlay_snapshot: input, tab_size, max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, @@ -34,20 +32,20 @@ impl TabMap { pub fn sync( &self, - editor_addition_snapshot: EditorAdditionSnapshot, - mut suggestion_edits: Vec, + inlay_snapshot: InlaySnapshot, + mut suggestion_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); let mut new_snapshot = TabSnapshot { - editor_addition_snapshot, + inlay_snapshot, tab_size, max_expansion_column: old_snapshot.max_expansion_column, version: old_snapshot.version, }; - if old_snapshot.editor_addition_snapshot.version - != new_snapshot.editor_addition_snapshot.version + if old_snapshot.inlay_snapshot.version + != new_snapshot.inlay_snapshot.version { new_snapshot.version += 1; } @@ -60,21 +58,21 @@ impl TabMap { // boundary. for suggestion_edit in &mut suggestion_edits { let old_end = old_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.old.end); let old_end_row_successor_offset = - old_snapshot.editor_addition_snapshot.to_offset(cmp::min( - EditorAdditionPoint::new(old_end.row() + 1, 0), - old_snapshot.editor_addition_snapshot.max_point(), + old_snapshot.inlay_snapshot.to_offset(cmp::min( + InlayPoint::new(old_end.row() + 1, 0), + old_snapshot.inlay_snapshot.max_point(), )); let new_end = new_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.new.end); let mut offset_from_edit = 0; let mut first_tab_offset = None; let mut last_tab_with_changed_expansion_offset = None; - 'outer: for chunk in old_snapshot.editor_addition_snapshot.chunks( + 'outer: for chunk in old_snapshot.inlay_snapshot.chunks( suggestion_edit.old.end..old_end_row_successor_offset, false, None, @@ -128,16 +126,16 @@ impl TabMap { for suggestion_edit in suggestion_edits { let old_start = old_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.old.start); let old_end = old_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.old.end); let new_start = new_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.new.start); let new_end = new_snapshot - .editor_addition_snapshot + .inlay_snapshot .to_point(suggestion_edit.new.end); tab_edits.push(TabEdit { old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), @@ -159,7 +157,7 @@ impl TabMap { #[derive(Clone)] pub struct TabSnapshot { - pub editor_addition_snapshot: EditorAdditionSnapshot, + pub inlay_snapshot: InlaySnapshot, pub tab_size: NonZeroU32, pub max_expansion_column: u32, pub version: usize, @@ -167,15 +165,15 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.editor_addition_snapshot.buffer_snapshot() + self.inlay_snapshot.buffer_snapshot() } pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.to_tab_point(EditorAdditionPoint::new( + self.to_tab_point(InlayPoint::new( row, - self.editor_addition_snapshot.line_len(row), + self.inlay_snapshot.line_len(row), )) .0 .column @@ -189,10 +187,10 @@ impl TabSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let input_start = self.to_editor_addition_point(range.start, Bias::Left).0; - let input_end = self.to_editor_addition_point(range.end, Bias::Right).0; + let input_start = self.to_inlay_point(range.start, Bias::Left).0; + let input_end = self.to_inlay_point(range.end, Bias::Right).0; let input_summary = self - .editor_addition_snapshot + .inlay_snapshot .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; @@ -245,12 +243,12 @@ impl TabSnapshot { suggestion_highlight: Option, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = - self.to_editor_addition_point(range.start, Bias::Left); + self.to_inlay_point(range.start, Bias::Left); let input_column = input_start.column(); - let input_start = self.editor_addition_snapshot.to_offset(input_start); + let input_start = self.inlay_snapshot.to_offset(input_start); let input_end = self - .editor_addition_snapshot - .to_offset(self.to_editor_addition_point(range.end, Bias::Right).0); + .inlay_snapshot + .to_offset(self.to_inlay_point(range.end, Bias::Right).0); let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { range.end.column() - range.start.column() } else { @@ -258,7 +256,7 @@ impl TabSnapshot { }; TabChunks { - editor_addition_chunks: self.editor_addition_snapshot.chunks( + inlay_chunks: self.inlay_snapshot.chunks( input_start..input_end, language_aware, text_highlights, @@ -279,8 +277,8 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> editor_addition_map::EditorAdditionBufferRows<'_> { - self.editor_addition_snapshot.buffer_rows(row) + pub fn buffer_rows(&self, row: u32) -> inlay_map::InlayBufferRows<'_> { + self.inlay_snapshot.buffer_rows(row) } #[cfg(test)] @@ -291,37 +289,33 @@ impl TabSnapshot { } pub fn max_point(&self) -> TabPoint { - self.to_tab_point(self.editor_addition_snapshot.max_point()) + self.to_tab_point(self.inlay_snapshot.max_point()) } pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { self.to_tab_point( - self.editor_addition_snapshot - .clip_point(self.to_editor_addition_point(point, bias).0, bias), + self.inlay_snapshot + .clip_point(self.to_inlay_point(point, bias).0, bias), ) } - pub fn to_tab_point(&self, input: EditorAdditionPoint) -> TabPoint { + pub fn to_tab_point(&self, input: InlayPoint) -> TabPoint { let chars = self - .editor_addition_snapshot - .chars_at(EditorAdditionPoint::new(input.row(), 0)); + .inlay_snapshot + .chars_at(InlayPoint::new(input.row(), 0)); let expanded = self.expand_tabs(chars, input.column()); TabPoint::new(input.row(), expanded) } - pub fn to_editor_addition_point( - &self, - output: TabPoint, - bias: Bias, - ) -> (EditorAdditionPoint, u32, u32) { + pub fn to_inlay_point(&self, output: TabPoint, bias: Bias) -> (InlayPoint, u32, u32) { let chars = self - .editor_addition_snapshot - .chars_at(EditorAdditionPoint::new(output.row(), 0)); + .inlay_snapshot + .chars_at(InlayPoint::new(output.row(), 0)); let expanded = output.column(); let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( - EditorAdditionPoint::new(output.row(), collapsed as u32), + InlayPoint::new(output.row(), collapsed as u32), expanded_char_column, to_next_stop, ) @@ -329,32 +323,32 @@ impl TabSnapshot { pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { let fold_point = self - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .fold_snapshot .to_fold_point(point, bias); let suggestion_point = self - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .to_suggestion_point(fold_point); - let editor_addition_point = self - .editor_addition_snapshot - .to_editor_addition_point(suggestion_point); - self.to_tab_point(editor_addition_point) + let inlay_point = self + .inlay_snapshot + .to_inlay_point(suggestion_point); + self.to_tab_point(inlay_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { - let editor_addition_point = self.to_editor_addition_point(point, bias).0; + let inlay_point = self.to_inlay_point(point, bias).0; let suggestion_point = self - .editor_addition_snapshot - .to_suggestion_point(editor_addition_point, bias); + .inlay_snapshot + .to_suggestion_point(inlay_point, bias); let fold_point = self - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .to_fold_point(suggestion_point); fold_point.to_buffer_point( &self - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .fold_snapshot, ) @@ -516,7 +510,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { const SPACES: &str = " "; pub struct TabChunks<'a> { - editor_addition_chunks: EditorAdditionChunks<'a>, + inlay_chunks: InlayChunks<'a>, chunk: Chunk<'a>, column: u32, max_expansion_column: u32, @@ -532,7 +526,7 @@ impl<'a> Iterator for TabChunks<'a> { fn next(&mut self) -> Option { if self.chunk.text.is_empty() { - if let Some(chunk) = self.editor_addition_chunks.next() { + if let Some(chunk) = self.inlay_chunks.next() { self.chunk = chunk; if self.inside_leading_tab { self.chunk.text = &self.chunk.text[1..]; @@ -600,10 +594,7 @@ impl<'a> Iterator for TabChunks<'a> { mod tests { use super::*; use crate::{ - display_map::{ - editor_addition_map::EditorAdditionMap, fold_map::FoldMap, - suggestion_map::SuggestionMap, - }, + display_map::{fold_map::FoldMap, inlay_map::InlayMap, suggestion_map::SuggestionMap}, MultiBuffer, }; use rand::{prelude::StdRng, Rng}; @@ -614,8 +605,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); - let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); @@ -632,8 +623,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); - let (_, mut tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), output); @@ -657,15 +648,15 @@ mod tests { let input_point = Point::new(0, ix as u32); let output_point = Point::new(0, output.find(c).unwrap() as u32); assert_eq!( - tab_snapshot.to_tab_point(EditorAdditionPoint(input_point)), + tab_snapshot.to_tab_point(InlayPoint(input_point)), TabPoint(output_point), "to_tab_point({input_point:?})" ); assert_eq!( tab_snapshot - .to_editor_addition_point(TabPoint(output_point), Bias::Left) + .to_inlay_point(TabPoint(output_point), Bias::Left) .0, - EditorAdditionPoint(input_point), + InlayPoint(input_point), "to_suggestion_point({output_point:?})" ); } @@ -681,8 +672,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); - let (_, mut tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), input); @@ -696,8 +687,8 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot); - let (_, tab_snapshot) = TabMap::new(editor_addition_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); assert_eq!( chunks(&tab_snapshot, TabPoint::zero()), @@ -761,13 +752,10 @@ mod tests { let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (_, editor_addition_snapshot) = EditorAdditionMap::new(suggestion_snapshot.clone()); - log::info!( - "EditorAdditionMap text: {:?}", - editor_addition_snapshot.text() - ); + let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (tab_map, _) = TabMap::new(editor_addition_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); let text = text::Rope::from(tabs_snapshot.text().as_str()); @@ -805,7 +793,7 @@ mod tests { ); let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); - if tab_size.get() > 1 && editor_addition_snapshot.text().contains('\t') { + if tab_size.get() > 1 && inlay_snapshot.text().contains('\t') { actual_summary.longest_row = expected_summary.longest_row; actual_summary.longest_row_chars = expected_summary.longest_row_chars; } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 2ae11b3d564f7536f94f703b05df11e8dc5c5583..d0312d2b3ac0cc392ab7bb107834c3333eab1d96 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,5 @@ use super::{ - editor_addition_map::EditorAdditionBufferRows, + inlay_map::InlayBufferRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, TextHighlights, }; @@ -65,7 +65,7 @@ pub struct WrapChunks<'a> { #[derive(Clone)] pub struct WrapBufferRows<'a> { - input_buffer_rows: EditorAdditionBufferRows<'a>, + input_buffer_rows: InlayBufferRows<'a>, input_buffer_row: Option, output_row: u32, soft_wrapped: bool, @@ -762,17 +762,17 @@ impl WrapSnapshot { let mut prev_fold_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let editor_addition_point = self + let inlay_point = self .tab_snapshot - .to_editor_addition_point(tab_point, Bias::Left) + .to_inlay_point(tab_point, Bias::Left) .0; let suggestion_point = self .tab_snapshot - .editor_addition_snapshot - .to_suggestion_point(editor_addition_point, Bias::Left); + .inlay_snapshot + .to_suggestion_point(inlay_point, Bias::Left); let fold_point = self .tab_snapshot - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .to_fold_point(suggestion_point); if fold_point.row() == prev_fold_row && display_row != 0 { @@ -781,7 +781,7 @@ impl WrapSnapshot { let buffer_point = fold_point.to_buffer_point( &self .tab_snapshot - .editor_addition_snapshot + .inlay_snapshot .suggestion_snapshot .fold_snapshot, ); @@ -1049,8 +1049,7 @@ mod tests { use super::*; use crate::{ display_map::{ - editor_addition_map::EditorAdditionMap, fold_map::FoldMap, - suggestion_map::SuggestionMap, tab_map::TabMap, + fold_map::FoldMap, inlay_map::InlayMap, suggestion_map::SuggestionMap, tab_map::TabMap, }, MultiBuffer, }; @@ -1106,13 +1105,9 @@ mod tests { log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (editor_addition_map, editors_additions_snapshot) = - EditorAdditionMap::new(suggestion_snapshot.clone()); - log::info!( - "EditorAdditionsMap text: {:?}", - editors_additions_snapshot.text() - ); - let (tab_map, _) = TabMap::new(editors_additions_snapshot.clone(), tab_size); + let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + log::info!("InlaysMap text: {:?}", inlay_snapshot.text()); + let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); @@ -1160,10 +1155,10 @@ mod tests { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1174,10 +1169,10 @@ mod tests { 40..=59 => { let (suggestion_snapshot, suggestion_edits) = suggestion_map.randomly_mutate(&mut rng); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1201,10 +1196,10 @@ mod tests { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (editor_addition_snapshot, editor_addition_edits) = - editor_addition_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(editor_addition_snapshot, editor_addition_edits, tab_size); + tab_map.sync(inlay_snapshot, inlay_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); @@ -1252,7 +1247,7 @@ mod tests { if tab_size.get() == 1 || !wrapped_snapshot .tab_snapshot - .editor_addition_snapshot + .inlay_snapshot .text() .contains('\t') { From d506522eef277a4fc5ca6006e13d88ac62c6a9d2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 7 Jun 2023 23:39:58 +0300 Subject: [PATCH 055/169] Correctly pass inlay hints --- crates/editor/src/display_map.rs | 43 ++++++++-------------- crates/editor/src/display_map/inlay_map.rs | 10 ++--- crates/project/src/lsp_command.rs | 4 +- crates/project/src/project.rs | 2 +- 4 files changed, 22 insertions(+), 37 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ade36990dea5700b19c167fce2e9176e8b7d424e..67b3c19d784a0ae3d3f7aa572abbd4294e98eae0 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -100,13 +100,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); - let (inlay_snapshot, edits) = self - .inlay_map - .sync(suggestion_snapshot.clone(), edits); + let (inlay_snapshot, edits) = self.inlay_map.sync(suggestion_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (tab_snapshot, edits) = - self.tab_map - .sync(inlay_snapshot.clone(), edits, tab_size); + let (tab_snapshot, edits) = self.tab_map.sync(inlay_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); @@ -293,24 +289,24 @@ impl DisplayMap { new_hints: &[project::InlayHint], cx: &mut ModelContext, ) { - dbg!("---", new_hints.len()); let multi_buffer = self.buffer.read(cx); - let zz = dbg!(multi_buffer + // TODO kb carry both remote and local ids of the buffer? + // now, `.buffer` requires remote id, hence this map. + let buffers_to_local_id = multi_buffer .all_buffers() .into_iter() - .map(|buffer_handle| buffer_handle.id()) - .collect::>()); + .map(|buffer_handle| (buffer_handle.id(), buffer_handle)) + .collect::>(); self.inlay_map.set_inlay_hints( new_hints .into_iter() .filter_map(|hint| { - // TODO kb this is all wrong, need to manage both(?) or at least the remote buffer id when needed. - // Here though, `.buffer(` requires remote buffer id, so use the workaround above. - dbg!(zz.contains(&(hint.buffer_id as usize))); - let buffer = dbg!(multi_buffer.buffer(dbg!(hint.buffer_id)))?.read(cx); - let snapshot = buffer.snapshot(); + let snapshot = buffers_to_local_id + .get(&hint.buffer_id)? + .read(cx) + .snapshot(); Some(InlayHintToRender { position: inlay_map::InlayPoint(text::ToPoint::to_point( &hint.position, @@ -419,9 +415,7 @@ impl DisplaySnapshot { fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { let fold_point = self.fold_snapshot.to_fold_point(point, bias); let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - let inlay_point = self - .inlay_snapshot - .to_inlay_point(suggestion_point); + let inlay_point = self.inlay_snapshot.to_inlay_point(suggestion_point); let tab_point = self.tab_snapshot.to_tab_point(inlay_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); @@ -432,13 +426,8 @@ impl DisplaySnapshot { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); - let inlay_point = self - .tab_snapshot - .to_inlay_point(tab_point, bias) - .0; - let suggestion_point = self - .inlay_snapshot - .to_suggestion_point(inlay_point, bias); + let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, bias).0; + let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point, bias); let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_point(&self.fold_snapshot) } @@ -853,9 +842,7 @@ impl DisplayPoint { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let inlay_point = map.tab_snapshot.to_inlay_point(tab_point, bias).0; - let suggestion_point = map - .inlay_snapshot - .to_suggestion_point(inlay_point, bias); + let suggestion_point = map.inlay_snapshot.to_suggestion_point(inlay_point, bias); let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_offset(&map.fold_snapshot) } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 18ac59fceff2ece8f55e0cb9b4ec2d39dd8d3713..afb4e28de3c1f8dd6ffeea600b3399d3f4fd30b1 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -29,12 +29,12 @@ pub struct InlayHintId(usize); pub struct InlayMap { snapshot: Mutex, next_hint_id: AtomicUsize, - hints: HashMap, + inlay_hints: HashMap, } #[derive(Clone)] pub struct InlaySnapshot { - // TODO kb merge these two together + // TODO kb merge these two together? pub suggestion_snapshot: SuggestionSnapshot, transforms: SumTree, pub version: usize, @@ -141,7 +141,7 @@ impl InlayMap { Self { snapshot: Mutex::new(snapshot.clone()), next_hint_id: AtomicUsize::new(0), - hints: HashMap::default(), + inlay_hints: HashMap::default(), }, snapshot, ) @@ -160,7 +160,6 @@ impl InlayMap { let mut inlay_edits = Vec::new(); - dbg!(&suggestion_edits); for suggestion_edit in suggestion_edits { let old = suggestion_edit.old; let new = suggestion_edit.new; @@ -190,9 +189,8 @@ impl InlayMap { } pub fn set_inlay_hints(&mut self, new_hints: Vec) { - dbg!(new_hints.len()); // TODO kb reuse ids for hints that did not change and similar things - self.hints = new_hints + self.inlay_hints = new_hints .into_iter() .map(|hint| { ( diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index e735773f4b244a1603ed61ac7aa3691eadd35bc8..3089683cd5de5916ca8df688491d23b0459e4c84 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1834,7 +1834,7 @@ impl LspCommand for InlayHints { .unwrap_or_default() .into_iter() .map(|lsp_hint| InlayHint { - buffer_id: buffer.id() as u64, + buffer_id: buffer.id(), position: origin_buffer.anchor_after( origin_buffer .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), @@ -2007,7 +2007,7 @@ impl LspCommand for InlayHints { let mut hints = Vec::new(); for message_hint in message.hints { let hint = InlayHint { - buffer_id: buffer.id() as u64, + buffer_id: buffer.id(), position: message_hint .position .and_then(language::proto::deserialize_anchor) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c33b563ea5516caf7fbf883aacd801267114f207..48d07af78e4a4c29e5ecbcb8dcbe45b00924f8f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -329,7 +329,7 @@ pub struct Location { #[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHint { - pub buffer_id: u64, + pub buffer_id: usize, pub position: Anchor, pub label: InlayHintLabel, pub kind: Option, From 7397b8028ccb237e35c7904f2e4462fd6223eb86 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 8 Jun 2023 00:22:15 +0300 Subject: [PATCH 056/169] Simplify inlay hint version handling --- crates/editor/src/display_map/block_map.rs | 6 +- crates/editor/src/display_map/tab_map.rs | 37 ++-- crates/editor/src/display_map/wrap_map.rs | 8 +- crates/editor/src/editor.rs | 186 ++++++++++----------- crates/project/src/project.rs | 4 +- 5 files changed, 108 insertions(+), 133 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e37c36f5ac29c10efe6b2652085f16701081d278..d2c2d11e1dd2d5ee1bcbdf8b8da38afe7cc1bfe5 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1181,8 +1181,7 @@ mod tests { fold_map.read(buffer_snapshot, subscription.consume().into_inner()); let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot, fold_edits); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(suggestion_snapshot, suggestion_edits); let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -1396,8 +1395,7 @@ mod tests { suggestion_map.sync(fold_snapshot, fold_edits); let (inlay_snapshot, inlay_edits) = inlay_map.sync(suggestion_snapshot, suggestion_edits); - let (tab_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 4716d625802444985ad347b1a81f1120cbc91f00..d41b616d626bf7356349772f36eb3a52f61c8551 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -44,9 +44,7 @@ impl TabMap { version: old_snapshot.version, }; - if old_snapshot.inlay_snapshot.version - != new_snapshot.inlay_snapshot.version - { + if old_snapshot.inlay_snapshot.version != new_snapshot.inlay_snapshot.version { new_snapshot.version += 1; } @@ -60,11 +58,10 @@ impl TabMap { let old_end = old_snapshot .inlay_snapshot .to_point(suggestion_edit.old.end); - let old_end_row_successor_offset = - old_snapshot.inlay_snapshot.to_offset(cmp::min( - InlayPoint::new(old_end.row() + 1, 0), - old_snapshot.inlay_snapshot.max_point(), - )); + let old_end_row_successor_offset = old_snapshot.inlay_snapshot.to_offset(cmp::min( + InlayPoint::new(old_end.row() + 1, 0), + old_snapshot.inlay_snapshot.max_point(), + )); let new_end = new_snapshot .inlay_snapshot .to_point(suggestion_edit.new.end); @@ -171,12 +168,9 @@ impl TabSnapshot { pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.to_tab_point(InlayPoint::new( - row, - self.inlay_snapshot.line_len(row), - )) - .0 - .column + self.to_tab_point(InlayPoint::new(row, self.inlay_snapshot.line_len(row))) + .0 + .column } else { max_point.column() } @@ -331,27 +325,18 @@ impl TabSnapshot { .inlay_snapshot .suggestion_snapshot .to_suggestion_point(fold_point); - let inlay_point = self - .inlay_snapshot - .to_inlay_point(suggestion_point); + let inlay_point = self.inlay_snapshot.to_inlay_point(suggestion_point); self.to_tab_point(inlay_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { let inlay_point = self.to_inlay_point(point, bias).0; - let suggestion_point = self - .inlay_snapshot - .to_suggestion_point(inlay_point, bias); + let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point, bias); let fold_point = self .inlay_snapshot .suggestion_snapshot .to_fold_point(suggestion_point); - fold_point.to_buffer_point( - &self - .inlay_snapshot - .suggestion_snapshot - .fold_snapshot, - ) + fold_point.to_buffer_point(&self.inlay_snapshot.suggestion_snapshot.fold_snapshot) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index d0312d2b3ac0cc392ab7bb107834c3333eab1d96..f976fd924befad56415f8f01bac76d3c7d5108f1 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -762,10 +762,7 @@ impl WrapSnapshot { let mut prev_fold_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let inlay_point = self - .tab_snapshot - .to_inlay_point(tab_point, Bias::Left) - .0; + let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, Bias::Left).0; let suggestion_point = self .tab_snapshot .inlay_snapshot @@ -1198,8 +1195,7 @@ mod tests { log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); let (inlay_snapshot, inlay_edits) = inlay_map.sync(suggestion_snapshot, suggestion_edits); - let (tabs_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + let (tabs_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3cc33ac03fdac730206ba22169cf13aca7346831..bb4f0e9c3d6d7c0f164218fed8e5bc90227ee8ba 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -26,7 +26,7 @@ use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; use clock::{Global, ReplicaId}; -use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque}; +use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; @@ -71,7 +71,6 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use parking_lot::RwLock; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, @@ -537,7 +536,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_hints_version: InlayHintVersion, + inlay_hint_versions: InlayHintVersions, _subscriptions: Vec, } @@ -1154,54 +1153,29 @@ impl CopilotState { } } +// TODO kb #[derive(Debug, Default, Clone)] -struct InlayHintVersion(Arc>>); +struct InlayHintVersions { + last_buffer_versions_with_hints: HashMap, +} -impl InlayHintVersion { - fn is_newer(&self, timestamp: &HashMap) -> bool { - let current_timestamp = self.0.read(); - Self::first_timestamp_newer(timestamp, ¤t_timestamp) +impl InlayHintVersions { + fn absent_or_newer(&self, buffer_id: usize, new_version: &Global) -> bool { + self.last_buffer_versions_with_hints + .get(&buffer_id) + .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints)) + .unwrap_or(true) } - fn update_if_newer(&self, new_timestamp: HashMap) -> bool { - let mut guard = self.0.write(); - if Self::first_timestamp_newer(&new_timestamp, &guard) { - *guard = new_timestamp; + fn insert(&mut self, buffer_id: usize, new_version: Global) -> bool { + if self.absent_or_newer(buffer_id, &new_version) { + self.last_buffer_versions_with_hints + .insert(buffer_id, new_version); true } else { false } } - - fn first_timestamp_newer( - first: &HashMap, - second: &HashMap, - ) -> bool { - if first.is_empty() { - false - } else if second.is_empty() { - true - } else { - let mut first_newer = false; - let mut second_has_extra_buffers = false; - for (buffer_id, first_version) in first { - match second.get(buffer_id) { - None => { - second_has_extra_buffers = true; - } - Some(second_version) => { - if second_version.changed_since(&first_version) { - return false; - } else if first_version.changed_since(&second_version) { - first_newer = true; - } - } - } - } - - first_newer || !second_has_extra_buffers - } - } } #[derive(Debug)] @@ -1402,7 +1376,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hints_version: InlayHintVersion::default(), + inlay_hint_versions: InlayHintVersions::default(), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2643,69 +2617,91 @@ impl Editor { return; } - let mut hint_fetch_tasks = Vec::new(); - let new_timestamp = self.buffer().read(cx).all_buffers().into_iter().fold( - HashMap::default(), - |mut buffer_versions, new_buffer| { - let new_buffer_version = new_buffer.read(cx).version(); - match buffer_versions.entry(new_buffer.id()) { - hash_map::Entry::Occupied(mut entry) => { - let entry_version = entry.get(); - if new_buffer_version.changed_since(&entry_version) { - entry.insert(new_buffer_version); - } - } - hash_map::Entry::Vacant(v) => { - v.insert(new_buffer_version); - } - } - - hint_fetch_tasks.push(cx.spawn(|editor, mut cx| async move { - let task = editor + let hint_fetch_tasks = self + .buffer() + .read(cx) + .all_buffers() + .into_iter() + .map(|buffer_handle| { + let buffer_id = buffer_handle.id(); + cx.spawn(|editor, mut cx| async move { + let task_data = editor .update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { + editor.project.as_ref().and_then(|project| { project.update(cx, |project, cx| { - let end = new_buffer.read(cx).len(); - project.inlay_hints(new_buffer, 0..end, cx) + let buffer = buffer_handle.read(cx); + let end = buffer.len(); + let version = buffer.version(); + + if editor + .inlay_hint_versions + .absent_or_newer(buffer_id, &version) + { + Some(( + version, + project.inlay_hints_for_buffer( + buffer_handle, + 0..end, + cx, + ), + )) + } else { + None + } }) }) }) .context("inlay hints fecth task spawn")?; - match task { - Some(task) => Ok(task.await.context("inlay hints fetch task await")?), - None => anyhow::Ok(Vec::new()), - } - })); - - buffer_versions - }, - ); + anyhow::Ok(( + buffer_id, + match task_data { + Some((buffer_version, task)) => Some(( + buffer_version, + task.await.context("inlay hints for buffer task")?, + )), + None => None, + }, + )) + }) + }) + .collect::>(); - let current_hints_version = self.inlay_hints_version.clone(); - if current_hints_version.is_newer(&new_timestamp) { - cx.spawn(|editor, mut cx| async move { - let mut new_hints = Vec::new(); - for task_result in futures::future::join_all(hint_fetch_tasks).await { - match task_result { - Ok(task_hints) => new_hints.extend(task_hints), - Err(e) => error!("Failed to update hints for buffer: {e:#}"), + cx.spawn(|editor, mut cx| async move { + let mut new_hints = Vec::new(); + for task_result in futures::future::join_all(hint_fetch_tasks).await { + match task_result { + Ok((_buffer_id, None)) => {} + Ok((buffer_id, Some((buffer_with_hints_version, buffer_hints)))) => { + let should_update_hints = editor + .update(&mut cx, |editor, _| { + editor + .inlay_hint_versions + .insert(buffer_id, buffer_with_hints_version) + }) + .log_err() + .unwrap_or(false); + if should_update_hints { + new_hints.extend(buffer_hints); + } } + Err(e) => error!("Failed to update hints for buffer: {e:#}"), } + } - if current_hints_version.update_if_newer(new_timestamp) { - editor - .update(&mut cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.set_inlay_hints(&new_hints, cx) - }); - }) - .log_err() - .unwrap_or(()) - } - }) - .detach(); - } + // TODO kb wrong, need a splice here instead + if !new_hints.is_empty() { + editor + .update(&mut cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.set_inlay_hints(&new_hints, cx); + }); + }) + .log_err() + .unwrap_or(()) + } + }) + .detach(); } fn trigger_on_type_formatting( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 48d07af78e4a4c29e5ecbcb8dcbe45b00924f8f9..856bf523f7bf3c71fedc43c378fa35ad9445bb74 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4904,7 +4904,7 @@ impl Project { ) } - pub fn inlay_hints( + pub fn inlay_hints_for_buffer( &self, buffer_handle: ModelHandle, range: Range, @@ -6688,7 +6688,7 @@ impl Project { let buffer_hints = this .update(&mut cx, |project, cx| { let end = buffer.read(cx).len(); - project.inlay_hints(buffer, 0..end, cx) + project.inlay_hints_for_buffer(buffer, 0..end, cx) }) .await .context("inlay hints fetch")?; From b193d62a5d2b6afb01a478a47c02f22d5feca1b1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 8 Jun 2023 13:09:31 +0300 Subject: [PATCH 057/169] Initial InlayMap tests and splice fn impl Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 43 ++-- crates/editor/src/display_map/inlay_map.rs | 279 ++++++++++++++++++--- 2 files changed, 267 insertions(+), 55 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 67b3c19d784a0ae3d3f7aa572abbd4294e98eae0..ec768211079a4d2d11775307921b67186ff9ae0d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -30,8 +30,6 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; -use self::inlay_map::InlayHintToRender; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -299,24 +297,29 @@ impl DisplayMap { .map(|buffer_handle| (buffer_handle.id(), buffer_handle)) .collect::>(); - self.inlay_map.set_inlay_hints( - new_hints - .into_iter() - .filter_map(|hint| { - let snapshot = buffers_to_local_id - .get(&hint.buffer_id)? - .read(cx) - .snapshot(); - Some(InlayHintToRender { - position: inlay_map::InlayPoint(text::ToPoint::to_point( - &hint.position, - &snapshot, - )), - text: hint.text().trim_end().into(), - }) - }) - .collect(), - ) + // multi_buffer.anchor_in_excerpt(excerpt_id, hint.position); + // TODO kb !!! rework things from buffer_id to excerpt_id + // let hint_anchor = multi_buffer + // .snapshot(cx) + // .anchor_in_excerpt(excerpt_id, hint.position); + + // self.inlay_map.splice( + // vec![], + // new_hints + // .into_iter() + // .filter_map(|hint| { + // let snapshot = buffers_to_local_id + // .get(&hint.buffer_id)? + // .read(cx) + // .snapshot(); + // Some(Inlay { + // position: hint.position, + // text: hint.text().trim_end().into(), + // }) + // }) + // .collect(), + // ) + todo!("TODO kb") } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index afb4e28de3c1f8dd6ffeea600b3399d3f4fd30b1..61d67584fc7f21cde2c1fb0931b2e3914bec8fa6 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -2,11 +2,12 @@ // TODO kb use std::{ + cmp::Reverse, ops::{Add, AddAssign, Range, Sub}, sync::atomic::{self, AtomicUsize}, }; -use crate::MultiBufferSnapshot; +use crate::{Anchor, MultiBufferSnapshot, ToOffset, ToPoint}; use super::{ suggestion_map::{ @@ -15,21 +16,22 @@ use super::{ }, TextHighlights, }; -use collections::HashMap; +use collections::{BTreeMap, HashMap, HashSet}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; use project::InlayHint; use rand::Rng; use sum_tree::{Bias, SumTree}; +use util::post_inc; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InlayHintId(usize); +pub struct InlayId(usize); pub struct InlayMap { snapshot: Mutex, - next_hint_id: AtomicUsize, - inlay_hints: HashMap, + next_inlay_id: usize, + inlays: HashMap, } #[derive(Clone)] @@ -41,16 +43,40 @@ pub struct InlaySnapshot { } #[derive(Clone)] -struct Transform { - input: TextSummary, - output: TextSummary, +enum Transform { + Isomorphic(TextSummary), + Inlay(Inlay), } impl sum_tree::Item for Transform { - type Summary = TextSummary; + type Summary = TransformSummary; fn summary(&self) -> Self::Summary { - self.output.clone() + match self { + Transform::Isomorphic(summary) => TransformSummary { + input: summary.clone(), + output: summary.clone(), + }, + Transform::Inlay(inlay) => TransformSummary { + input: TextSummary::default(), + output: inlay.properties.text.summary(), + }, + } + } +} + +#[derive(Clone, Debug, Default)] +struct TransformSummary { + input: TextSummary, + output: TextSummary, +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; } } @@ -84,6 +110,18 @@ impl AddAssign for InlayOffset { #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct InlayPoint(pub Point); +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.lines; + } +} + #[derive(Clone)] pub struct InlayBufferRows<'a> { suggestion_rows: SuggestionBufferRows<'a>, @@ -94,8 +132,14 @@ pub struct InlayChunks<'a> { } #[derive(Debug, Clone)] -pub struct InlayHintToRender { - pub(super) position: InlayPoint, +pub struct Inlay { + pub(super) id: InlayId, + pub(super) properties: InlayProperties, +} + +#[derive(Debug, Clone)] +pub struct InlayProperties { + pub(super) position: Anchor, pub(super) text: Rope, } @@ -140,8 +184,8 @@ impl InlayMap { ( Self { snapshot: Mutex::new(snapshot.clone()), - next_hint_id: AtomicUsize::new(0), - inlay_hints: HashMap::default(), + next_inlay_id: 0, + inlays: HashMap::default(), }, snapshot, ) @@ -160,6 +204,8 @@ impl InlayMap { let mut inlay_edits = Vec::new(); + dbg!(self.inlays.len()); + for suggestion_edit in suggestion_edits { let old = suggestion_edit.old; let new = suggestion_edit.new; @@ -175,30 +221,116 @@ impl InlayMap { (snapshot.clone(), inlay_edits) } - // TODO kb replace set_inlay_hints with this pub fn splice( &mut self, - to_remove: Vec, - to_insert: Vec, - ) -> Vec { - // Order removals and insertions by position. - // let anchors; - - // Remove and insert inlays in a single traversal across the tree. - todo!("TODO kb") - } + to_remove: HashSet, + to_insert: Vec, + ) -> (InlaySnapshot, Vec, Vec) { + let mut snapshot = self.snapshot.lock(); - pub fn set_inlay_hints(&mut self, new_hints: Vec) { - // TODO kb reuse ids for hints that did not change and similar things - self.inlay_hints = new_hints - .into_iter() - .map(|hint| { - ( - InlayHintId(self.next_hint_id.fetch_add(1, atomic::Ordering::SeqCst)), - hint, - ) - }) - .collect(); + let mut inlays = BTreeMap::new(); + let mut new_ids = Vec::new(); + for properties in to_insert { + let inlay = Inlay { + id: InlayId(post_inc(&mut self.next_inlay_id)), + properties, + }; + self.inlays.insert(inlay.id, inlay.clone()); + new_ids.push(inlay.id); + + let buffer_point = inlay + .properties + .position + .to_point(snapshot.buffer_snapshot()); + let fold_point = snapshot + .suggestion_snapshot + .fold_snapshot + .to_fold_point(buffer_point, Bias::Left); + let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); + let inlay_point = snapshot.to_inlay_point(suggestion_point); + + inlays.insert((inlay_point, Reverse(inlay.id)), Some(inlay)); + } + + for inlay_id in to_remove { + if let Some(inlay) = self.inlays.remove(&inlay_id) { + let buffer_point = inlay + .properties + .position + .to_point(snapshot.buffer_snapshot()); + let fold_point = snapshot + .suggestion_snapshot + .fold_snapshot + .to_fold_point(buffer_point, Bias::Left); + let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); + let inlay_point = snapshot.to_inlay_point(suggestion_point); + inlays.insert((inlay_point, Reverse(inlay.id)), None); + } + } + + let mut new_transforms = SumTree::new(); + let mut cursor = snapshot + .transforms + .cursor::<(InlayPoint, SuggestionPoint)>(); + for ((inlay_point, inlay_id), inlay) in inlays { + new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &()); + while let Some(transform) = cursor.item() { + match transform { + Transform::Isomorphic(_) => break, + Transform::Inlay(inlay) => { + if inlay.id > inlay_id.0 { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + } else { + if inlay.id == inlay_id.0 { + cursor.next(&()); + } + break; + } + } + } + } + + if let Some(inlay) = inlay { + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + let prefix = inlay_point.0 - cursor.start().0 .0; + if !prefix.is_zero() { + let prefix_suggestion_start = cursor.start().1; + let prefix_suggestion_end = SuggestionPoint(cursor.start().1 .0 + prefix); + new_transforms.push( + Transform::Isomorphic( + snapshot.suggestion_snapshot.text_summary_for_range( + prefix_suggestion_start..prefix_suggestion_end, + ), + ), + &(), + ); + } + + new_transforms.push(Transform::Inlay(inlay), &()); + + let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 + prefix); + let suffix_suggestion_end = cursor.end(&()).1; + new_transforms.push( + Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range( + suffix_suggestion_start..suffix_suggestion_end, + )), + &(), + ); + + cursor.next(&()); + } else { + new_transforms.push(Transform::Inlay(inlay), &()); + } + } + } + + new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); + snapshot.transforms = new_transforms; + snapshot.version += 1; + + (snapshot.clone(), Vec::new(), new_ids) } } @@ -295,3 +427,80 @@ impl InlaySnapshot { self.suggestion_snapshot.text() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, + MultiBuffer, + }; + use gpui::AppContext; + + #[gpui::test] + fn test_basic_inlays(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abcdefghi", cx); + let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); + let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); + let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + assert_eq!(inlay_snapshot.text(), "abcdefghi"); + + let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( + HashSet::default(), + vec![InlayProperties { + position: buffer.read(cx).read(cx).anchor_before(3), + text: "|123|".into(), + }], + ); + assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); + + buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx)); + let (fold_snapshot, fold_edits) = fold_map.read( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); + + //////// case: folding and unfolding the text should hine and then return the hint back + let (mut fold_map_writer, _, _) = fold_map.write( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi"); + + let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); + + ////////// case: replacing the anchor that got the hint: it should disappear, then undo and it should reappear again + buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx)); + let (fold_snapshot, fold_edits) = fold_map.read( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "XYZabCdefghi"); + + buffer.update(cx, |buffer, cx| buffer.undo(cx)); + let (fold_snapshot, fold_edits) = fold_map.read( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); + } +} From 3028767d12a8eb1457cb3f469bfc8b6060ed61ca Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 8 Jun 2023 15:44:20 +0300 Subject: [PATCH 058/169] Improve on inlya locations --- crates/editor/src/display_map.rs | 15 +-- crates/editor/src/display_map/inlay_map.rs | 26 ++++-- crates/editor/src/editor.rs | 101 +++++++++++---------- 3 files changed, 75 insertions(+), 67 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ec768211079a4d2d11775307921b67186ff9ae0d..e8e199b233b7db4fdb61cfdfd21e7e94eb124723 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -5,7 +5,9 @@ mod suggestion_map; mod tab_map; mod wrap_map; -use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; +use crate::{ + Anchor, AnchorRangeExt, InlayHintLocation, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, +}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::{FoldMap, FoldOffset}; @@ -284,19 +286,12 @@ impl DisplayMap { pub fn set_inlay_hints( &mut self, - new_hints: &[project::InlayHint], + new_hints: &HashMap>, cx: &mut ModelContext, ) { + // TODO kb map this to Anchor and set to the map let multi_buffer = self.buffer.read(cx); - // TODO kb carry both remote and local ids of the buffer? - // now, `.buffer` requires remote id, hence this map. - let buffers_to_local_id = multi_buffer - .all_buffers() - .into_iter() - .map(|buffer_handle| (buffer_handle.id(), buffer_handle)) - .collect::>(); - // multi_buffer.anchor_in_excerpt(excerpt_id, hint.position); // TODO kb !!! rework things from buffer_id to excerpt_id // let hint_anchor = multi_buffer diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 61d67584fc7f21cde2c1fb0931b2e3914bec8fa6..a6b6d2f6bc3e5d35c629be7da397a5383980e0fd 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -7,7 +7,7 @@ use std::{ sync::atomic::{self, AtomicUsize}, }; -use crate::{Anchor, MultiBufferSnapshot, ToOffset, ToPoint}; +use crate::{Anchor, ExcerptId, InlayHintLocation, MultiBufferSnapshot, ToOffset, ToPoint}; use super::{ suggestion_map::{ @@ -31,7 +31,7 @@ pub struct InlayId(usize); pub struct InlayMap { snapshot: Mutex, next_inlay_id: usize, - inlays: HashMap, + inlays: HashMap, } #[derive(Clone)] @@ -224,18 +224,18 @@ impl InlayMap { pub fn splice( &mut self, to_remove: HashSet, - to_insert: Vec, + to_insert: Vec<(InlayHintLocation, InlayProperties)>, ) -> (InlaySnapshot, Vec, Vec) { let mut snapshot = self.snapshot.lock(); let mut inlays = BTreeMap::new(); let mut new_ids = Vec::new(); - for properties in to_insert { + for (location, properties) in to_insert { let inlay = Inlay { id: InlayId(post_inc(&mut self.next_inlay_id)), properties, }; - self.inlays.insert(inlay.id, inlay.clone()); + self.inlays.insert(inlay.id, (location, inlay.clone())); new_ids.push(inlay.id); let buffer_point = inlay @@ -253,7 +253,7 @@ impl InlayMap { } for inlay_id in to_remove { - if let Some(inlay) = self.inlays.remove(&inlay_id) { + if let Some((_, inlay)) = self.inlays.remove(&inlay_id) { let buffer_point = inlay .properties .position @@ -448,10 +448,16 @@ mod tests { let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( HashSet::default(), - vec![InlayProperties { - position: buffer.read(cx).read(cx).anchor_before(3), - text: "|123|".into(), - }], + vec![( + InlayHintLocation { + buffer_id: 0, + excerpt_id: ExcerptId::default(), + }, + InlayProperties { + position: buffer.read(cx).read(cx).anchor_before(3), + text: "|123|".into(), + }, + )], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb4f0e9c3d6d7c0f164218fed8e5bc90227ee8ba..b5d963efa4f2bd341947d4d243a30a45d2780c23 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1153,24 +1153,30 @@ impl CopilotState { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct InlayHintLocation { + pub buffer_id: u64, + pub excerpt_id: ExcerptId, +} + // TODO kb #[derive(Debug, Default, Clone)] struct InlayHintVersions { - last_buffer_versions_with_hints: HashMap, + last_buffer_versions_with_hints: HashMap, } impl InlayHintVersions { - fn absent_or_newer(&self, buffer_id: usize, new_version: &Global) -> bool { + fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool { self.last_buffer_versions_with_hints - .get(&buffer_id) + .get(location) .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints)) .unwrap_or(true) } - fn insert(&mut self, buffer_id: usize, new_version: Global) -> bool { - if self.absent_or_newer(buffer_id, &new_version) { + fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool { + if self.absent_or_newer(&location, &new_version) { self.last_buffer_versions_with_hints - .insert(buffer_id, new_version); + .insert(location, new_version); true } else { false @@ -2617,50 +2623,40 @@ impl Editor { return; } - let hint_fetch_tasks = self - .buffer() - .read(cx) - .all_buffers() - .into_iter() - .map(|buffer_handle| { - let buffer_id = buffer_handle.id(); + let multi_buffer = self.buffer().read(cx); + let buffer_snapshot = multi_buffer.snapshot(cx); + let hint_fetch_tasks = buffer_snapshot + .excerpts() + .map(|(excerpt_id, excerpt_buffer_snapshot, _)| { + (excerpt_id, excerpt_buffer_snapshot.clone()) + }) + .map(|(excerpt_id, excerpt_buffer_snapshot)| { cx.spawn(|editor, mut cx| async move { - let task_data = editor + let task = editor .update(&mut cx, |editor, cx| { editor.project.as_ref().and_then(|project| { project.update(cx, |project, cx| { - let buffer = buffer_handle.read(cx); - let end = buffer.len(); - let version = buffer.version(); - - if editor - .inlay_hint_versions - .absent_or_newer(buffer_id, &version) - { - Some(( - version, - project.inlay_hints_for_buffer( - buffer_handle, - 0..end, - cx, - ), - )) - } else { - None - } + Some( + project.inlay_hints_for_buffer( + editor + .buffer() + .read(cx) + .buffer(excerpt_buffer_snapshot.remote_id())?, + 0..excerpt_buffer_snapshot.len(), + cx, + ), + ) }) }) }) .context("inlay hints fecth task spawn")?; anyhow::Ok(( - buffer_id, - match task_data { - Some((buffer_version, task)) => Some(( - buffer_version, - task.await.context("inlay hints for buffer task")?, - )), - None => None, + excerpt_id, + excerpt_buffer_snapshot, + match task { + Some(task) => task.await.context("inlay hints for buffer task")?, + None => Vec::new(), }, )) }) @@ -2668,21 +2664,32 @@ impl Editor { .collect::>(); cx.spawn(|editor, mut cx| async move { - let mut new_hints = Vec::new(); + let mut new_hints: HashMap> = + HashMap::default(); for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { - Ok((_buffer_id, None)) => {} - Ok((buffer_id, Some((buffer_with_hints_version, buffer_hints)))) => { + Ok((excerpt_id, excerpt_buffer_snapshot, excerpt_hints)) => { + let buffer_id = excerpt_buffer_snapshot.remote_id(); let should_update_hints = editor .update(&mut cx, |editor, _| { - editor - .inlay_hint_versions - .insert(buffer_id, buffer_with_hints_version) + editor.inlay_hint_versions.insert( + InlayHintLocation { + buffer_id, + excerpt_id, + }, + excerpt_buffer_snapshot.version().clone(), + ) }) .log_err() .unwrap_or(false); if should_update_hints { - new_hints.extend(buffer_hints); + new_hints + .entry(InlayHintLocation { + buffer_id, + excerpt_id, + }) + .or_default() + .extend(excerpt_hints); } } Err(e) => error!("Failed to update hints for buffer: {e:#}"), From 5ad85b44d65860b5eb0d373bb4bafb437277cdf7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 8 Jun 2023 16:44:01 +0300 Subject: [PATCH 059/169] Implement chunks of the InlayMap Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 124 +++++++++++++++--- .../editor/src/display_map/suggestion_map.rs | 4 + 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index a6b6d2f6bc3e5d35c629be7da397a5383980e0fd..8afd6d5fdc87d4087a72caf85ae6d56fcdc8778a 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -2,7 +2,7 @@ // TODO kb use std::{ - cmp::Reverse, + cmp::{self, Reverse}, ops::{Add, AddAssign, Range, Sub}, sync::atomic::{self, AtomicUsize}, }; @@ -22,7 +22,7 @@ use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; use project::InlayHint; use rand::Rng; -use sum_tree::{Bias, SumTree}; +use sum_tree::{Bias, Cursor, SumTree}; use util::post_inc; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -42,7 +42,7 @@ pub struct InlaySnapshot { pub version: usize, } -#[derive(Clone)] +#[derive(Clone, Debug)] enum Transform { Isomorphic(TextSummary), Inlay(Inlay), @@ -107,6 +107,18 @@ impl AddAssign for InlayOffset { } } +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.len; + } +} + #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct InlayPoint(pub Point); @@ -128,7 +140,12 @@ pub struct InlayBufferRows<'a> { } pub struct InlayChunks<'a> { + transforms: Cursor<'a, Transform, (InlayOffset, SuggestionOffset)>, suggestion_chunks: SuggestionChunks<'a>, + suggestion_chunk: Option>, + inlay_chunks: Option>, + output_offset: InlayOffset, + max_output_offset: InlayOffset, } #[derive(Debug, Clone)] @@ -147,7 +164,49 @@ impl<'a> Iterator for InlayChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { - self.suggestion_chunks.next() + if self.output_offset == self.max_output_offset { + return None; + } + + let chunk = match self.transforms.item()? { + Transform::Isomorphic(transform) => { + let chunk = self + .suggestion_chunk + .get_or_insert_with(|| self.suggestion_chunks.next().unwrap()); + if chunk.text.is_empty() { + *chunk = self.suggestion_chunks.next().unwrap(); + } + + let (prefix, suffix) = chunk.text.split_at(transform.len); + chunk.text = suffix; + self.output_offset.0 += prefix.len(); + Chunk { + text: prefix, + ..chunk.clone() + } + } + Transform::Inlay(inlay) => { + let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| { + let start = self.output_offset - self.transforms.start().0; + let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0) + - self.transforms.start().0; + inlay.properties.text.chunks_in_range(start.0..end.0) + }); + + let chunk = inlay_chunks.next().unwrap(); + self.output_offset.0 += chunk.len(); + Chunk { + text: chunk, + ..Default::default() + } + } + }; + + if self.output_offset == self.transforms.end(&()).0 { + self.transforms.next(&()); + } + + Some(chunk) } } @@ -178,7 +237,10 @@ impl InlayMap { let snapshot = InlaySnapshot { suggestion_snapshot: suggestion_snapshot.clone(), version: 0, - transforms: SumTree::new(), + transforms: SumTree::from_item( + Transform::Isomorphic(suggestion_snapshot.text_summary()), + &(), + ), }; ( @@ -348,9 +410,12 @@ impl InlaySnapshot { ) } + pub fn len(&self) -> InlayOffset { + InlayOffset(self.transforms.summary().output.len) + } + pub fn max_point(&self) -> InlayPoint { - // TODO kb copied from suggestion_map - self.to_inlay_point(self.suggestion_snapshot.max_point()) + InlayPoint(self.transforms.summary().output.lines) } pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { @@ -372,6 +437,19 @@ impl InlaySnapshot { SuggestionPoint(point.0) } + pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); + cursor.seek(&offset, Bias::Right, &()); + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let overshoot = offset - cursor.start().0; + cursor.start().1 + SuggestionOffset(overshoot.0) + } + Some(Transform::Inlay(inlay)) => cursor.start().1, + None => self.suggestion_snapshot.len(), + } + } + pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { InlayPoint(point.0) } @@ -410,21 +488,35 @@ impl InlaySnapshot { text_highlights: Option<&'a TextHighlights>, suggestion_highlight: Option, ) -> InlayChunks<'a> { - // TODO kb copied from suggestion_map + dbg!(self.transforms.items(&())); + + let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let suggestion_range = + self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end); + let suggestion_chunks = self.suggestion_snapshot.chunks( + suggestion_range, + language_aware, + text_highlights, + suggestion_highlight, + ); + InlayChunks { - suggestion_chunks: self.suggestion_snapshot.chunks( - SuggestionOffset(range.start.0)..SuggestionOffset(range.end.0), - language_aware, - text_highlights, - suggestion_highlight, - ), + transforms: cursor, + suggestion_chunks, + inlay_chunks: None, + suggestion_chunk: None, + output_offset: range.start, + max_output_offset: range.end, } } #[cfg(test)] pub fn text(&self) -> String { - // TODO kb copied from suggestion_map - self.suggestion_snapshot.text() + self.chunks(Default::default()..self.len(), false, None, None) + .map(|chunk| chunk.text) + .collect() } } diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index eac903d0af9c7e9c3d9a4bc184ce842e8d07cf52..b23f172bcaa6d6cf22505bdd9715d88e6f874217 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -358,6 +358,10 @@ impl SuggestionSnapshot { } } + pub fn text_summary(&self) -> TextSummary { + self.text_summary_for_range(Default::default()..self.max_point()) + } + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; From 5fadbf77d47ef27603c4e832f8ba42068a393e4f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 8 Jun 2023 17:39:04 +0300 Subject: [PATCH 060/169] Implement InlayHint sync method and fix the bugs Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 63 ++++++++++++++-------- crates/editor/src/editor.rs | 2 + crates/project/src/lsp_command.rs | 1 - 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 8afd6d5fdc87d4087a72caf85ae6d56fcdc8778a..d944074558434c2947b4c602afc6671134795677 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -177,7 +177,9 @@ impl<'a> Iterator for InlayChunks<'a> { *chunk = self.suggestion_chunks.next().unwrap(); } - let (prefix, suffix) = chunk.text.split_at(transform.len); + let (prefix, suffix) = chunk + .text + .split_at(cmp::min(transform.len, chunk.text.len())); chunk.text = suffix; self.output_offset.0 += prefix.len(); Chunk { @@ -264,23 +266,48 @@ impl InlayMap { snapshot.version += 1; } - let mut inlay_edits = Vec::new(); + let mut new_transforms = SumTree::new(); + let mut cursor = snapshot.transforms.cursor::(); + let mut suggestion_edits = suggestion_edits.iter().peekable(); + + while let Some(suggestion_edit) = suggestion_edits.next() { + if suggestion_edit.old.start >= *cursor.start() { + new_transforms.push_tree( + cursor.slice(&suggestion_edit.old.start, Bias::Right, &()), + &(), + ); + } - dbg!(self.inlays.len()); + if suggestion_edit.old.end > cursor.end(&()) { + cursor.seek_forward(&suggestion_edit.old.end, Bias::Right, &()); + } - for suggestion_edit in suggestion_edits { - let old = suggestion_edit.old; - let new = suggestion_edit.new; - // TODO kb copied from suggestion_map - inlay_edits.push(InlayEdit { - old: InlayOffset(old.start.0)..InlayOffset(old.end.0), - new: InlayOffset(old.start.0)..InlayOffset(new.end.0), - }) + let transform_start = SuggestionOffset(new_transforms.summary().input.len); + let mut transform_end = suggestion_edit.new.end; + if suggestion_edits + .peek() + .map_or(true, |edit| edit.old.start > cursor.end(&())) + { + transform_end += cursor.end(&()) - suggestion_edit.old.end; + cursor.next(&()); + } + new_transforms.push( + Transform::Isomorphic(suggestion_snapshot.text_summary_for_range( + suggestion_snapshot.to_point(transform_start) + ..suggestion_snapshot.to_point(transform_end), + )), + &(), + ); } + new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); + snapshot.suggestion_snapshot = suggestion_snapshot; + dbg!(new_transforms.items(&())); + snapshot.transforms = new_transforms; - (snapshot.clone(), inlay_edits) + (snapshot.clone(), Default::default()) } pub fn splice( @@ -580,7 +607,7 @@ mod tests { let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); - ////////// case: replacing the anchor that got the hint: it should disappear, then undo and it should reappear again + ////////// case: replacing the anchor that got the hint: it should disappear buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx)); let (fold_snapshot, fold_edits) = fold_map.read( buffer.read(cx).snapshot(cx), @@ -590,15 +617,5 @@ mod tests { suggestion_map.sync(fold_snapshot.clone(), fold_edits); let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); assert_eq!(inlay_snapshot.text(), "XYZabCdefghi"); - - buffer.update(cx, |buffer, cx| buffer.undo(cx)); - let (fold_snapshot, fold_edits) = fold_map.read( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); - assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b5d963efa4f2bd341947d4d243a30a45d2780c23..ffafef5cb936351e9dfdf0b7d366dee673aca31b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2672,6 +2672,8 @@ impl Editor { let buffer_id = excerpt_buffer_snapshot.remote_id(); let should_update_hints = editor .update(&mut cx, |editor, _| { + // TODO kb wrong: need to query hints per buffer, not per excerpt + // need to store the previous state and calculate the diff between them, and calculate anchors here too. editor.inlay_hint_versions.insert( InlayHintLocation { buffer_id, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 3089683cd5de5916ca8df688491d23b0459e4c84..85fec37eb882a383d2f9ce8ab53d80e4d390a724 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1917,7 +1917,6 @@ impl LspCommand for InlayHints { .end .and_then(language::proto::deserialize_anchor) .context("invalid end")?; - // TODO kb has it to be multiple versions instead? buffer .update(&mut cx, |buffer, _| { buffer.wait_for_version(deserialize_version(&message.version)) From daa2ebb57fe855ba636d4a8e0c45cc7e4bd3bf49 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 09:53:10 +0300 Subject: [PATCH 061/169] Calculate anchors for new hints --- crates/editor/src/display_map.rs | 50 +++++++++++----------- crates/editor/src/display_map/inlay_map.rs | 2 +- crates/editor/src/editor.rs | 3 +- crates/project/src/project.rs | 1 - 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e8e199b233b7db4fdb61cfdfd21e7e94eb124723..30c9b7e69fd3d54a5664580c7c480ce0321ce854 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -6,7 +6,8 @@ mod tab_map; mod wrap_map; use crate::{ - Anchor, AnchorRangeExt, InlayHintLocation, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, InlayHintLocation, + MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; @@ -284,37 +285,34 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn set_inlay_hints( + pub fn splice_inlay_hints( &mut self, new_hints: &HashMap>, cx: &mut ModelContext, ) { - // TODO kb map this to Anchor and set to the map let multi_buffer = self.buffer.read(cx); + let multi_snapshot = multi_buffer.snapshot(cx); + + let mut hints_to_add = Vec::new(); + for (&location, hints) in new_hints { + for hint in hints { + let hint_anchor = + multi_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); + hints_to_add.push(( + location, + InlayProperties { + position: hint_anchor, + text: hint.text().trim_end().into(), + }, + )) + } + } - // multi_buffer.anchor_in_excerpt(excerpt_id, hint.position); - // TODO kb !!! rework things from buffer_id to excerpt_id - // let hint_anchor = multi_buffer - // .snapshot(cx) - // .anchor_in_excerpt(excerpt_id, hint.position); - - // self.inlay_map.splice( - // vec![], - // new_hints - // .into_iter() - // .filter_map(|hint| { - // let snapshot = buffers_to_local_id - // .get(&hint.buffer_id)? - // .read(cx) - // .snapshot(); - // Some(Inlay { - // position: hint.position, - // text: hint.text().trim_end().into(), - // }) - // }) - // .collect(), - // ) - todo!("TODO kb") + self.inlay_map.splice( + // TODO kb this is wrong, calc diffs in the editor instead. + self.inlay_map.inlays.keys().copied().collect(), + hints_to_add, + ); } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index d944074558434c2947b4c602afc6671134795677..96ce0af74e3aa7dc8df7cd146248530ead0ac226 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -31,7 +31,7 @@ pub struct InlayId(usize); pub struct InlayMap { snapshot: Mutex, next_inlay_id: usize, - inlays: HashMap, + pub(super) inlays: HashMap, } #[derive(Clone)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ffafef5cb936351e9dfdf0b7d366dee673aca31b..9560aa707e043bea70820b7f1c2750b6ed174a1f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2698,12 +2698,11 @@ impl Editor { } } - // TODO kb wrong, need a splice here instead if !new_hints.is_empty() { editor .update(&mut cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { - display_map.set_inlay_hints(&new_hints, cx); + display_map.splice_inlay_hints(&new_hints, cx); }); }) .log_err() diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 856bf523f7bf3c71fedc43c378fa35ad9445bb74..ce42f6df114766534e55f74300f44bdbac2e143a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2825,7 +2825,6 @@ impl Project { language_server .on_request::({ - let this = this.downgrade(); move |(), mut cx| async move { let this = this .upgrade(&cx) From 568a67c4d7305d8d4a6a826ff0689acf530fcc03 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 10:34:23 +0300 Subject: [PATCH 062/169] Implement more InlaySnapshot methods Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 4 +- crates/editor/src/display_map/inlay_map.rs | 114 ++++++++++++++---- .../editor/src/display_map/suggestion_map.rs | 1 + crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/display_map/wrap_map.rs | 2 +- 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 30c9b7e69fd3d54a5664580c7c480ce0321ce854..5644d63c37b1eb61f6cf6afa311cf5715db5cd8d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -423,7 +423,7 @@ impl DisplaySnapshot { let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, bias).0; - let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point, bias); + let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point); let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_point(&self.fold_snapshot) } @@ -838,7 +838,7 @@ impl DisplayPoint { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let inlay_point = map.tab_snapshot.to_inlay_point(tab_point, bias).0; - let suggestion_point = map.inlay_snapshot.to_suggestion_point(inlay_point, bias); + let suggestion_point = map.inlay_snapshot.to_suggestion_point(inlay_point); let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); fold_point.to_buffer_offset(&map.fold_snapshot) } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 96ce0af74e3aa7dc8df7cd146248530ead0ac226..ed58e44432b0c683add3c893dea0873599a7d334 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -48,6 +48,12 @@ enum Transform { Inlay(Inlay), } +impl Transform { + fn is_inlay(&self) -> bool { + matches!(self, Self::Inlay(_)) + } +} + impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -425,16 +431,29 @@ impl InlayMap { impl InlaySnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - // TODO kb copied from suggestion_map self.suggestion_snapshot.buffer_snapshot() } pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { - // TODO kb copied from suggestion_map - self.to_inlay_point( - self.suggestion_snapshot - .to_point(super::suggestion_map::SuggestionOffset(offset.0)), - ) + let mut cursor = self + .transforms + .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = offset.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let suggestion_offset_start = cursor.start().1 .1; + let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot); + let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start); + let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end); + InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.properties.text.offset_to_point(overshoot); + InlayPoint(cursor.start().1 .0 .0 + overshoot) + } + None => self.max_point(), + } } pub fn len(&self) -> InlayOffset { @@ -446,22 +465,43 @@ impl InlaySnapshot { } pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { - // TODO kb copied from suggestion_map - InlayOffset( - self.suggestion_snapshot - .to_offset(self.to_suggestion_point(point, Bias::Left)) - .0, - ) + let mut cursor = self + .transforms + .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = point.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let suggestion_point_start = cursor.start().1 .1; + let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot); + let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start); + let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end); + InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.properties.text.point_to_offset(overshoot); + InlayOffset(cursor.start().1 .0 .0 + overshoot) + } + None => self.len(), + } } pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator { - self.suggestion_snapshot - .chars_at(self.to_suggestion_point(start, Bias::Left)) + self.chunks(self.to_offset(start)..self.len(), false, None, None) + .flat_map(|chunk| chunk.text.chars()) } - // TODO kb what to do with bias? - pub fn to_suggestion_point(&self, point: InlayPoint, _: Bias) -> SuggestionPoint { - SuggestionPoint(point.0) + pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = point.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + SuggestionPoint(cursor.start().1 .0 + overshoot) + } + Some(Transform::Inlay(inlay)) => cursor.start().1, + None => self.suggestion_snapshot.max_point(), + } } pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset { @@ -478,22 +518,46 @@ impl InlaySnapshot { } pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { - InlayPoint(point.0) + let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>(); + // TODO kb is the bias right? should we have an external one instead? + cursor.seek(&point, Bias::Right, &()); + let overshoot = point.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => InlayPoint(cursor.start().1 .0 + overshoot), + Some(Transform::Inlay(inlay)) => cursor.start().1, + None => self.max_point(), + } } pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { - // TODO kb copied from suggestion_map - self.to_inlay_point( - self.suggestion_snapshot - .clip_point(self.to_suggestion_point(point, bias), bias), - ) + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, bias, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => return point, + Some(Transform::Inlay(_)) => {} + None => cursor.prev(&()), + } + + while cursor + .item() + .map_or(false, |transform| transform.is_inlay()) + { + match bias { + Bias::Left => cursor.prev(&()), + Bias::Right => cursor.next(&()), + } + } + + match bias { + Bias::Left => cursor.end(&()), + Bias::Right => *cursor.start(), + } } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { // TODO kb copied from suggestion_map self.suggestion_snapshot.text_summary_for_range( - self.to_suggestion_point(range.start, Bias::Left) - ..self.to_suggestion_point(range.end, Bias::Left), + self.to_suggestion_point(range.start)..self.to_suggestion_point(range.end), ) } diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index b23f172bcaa6d6cf22505bdd9715d88e6f874217..1b188fe2ede5e40dda035acac2d33cc4fa4af14a 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -56,6 +56,7 @@ impl SuggestionPoint { } } + #[derive(Clone, Debug)] pub struct Suggestion { pub position: T, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index d41b616d626bf7356349772f36eb3a52f61c8551..5c2f05d67c8bc4f3b8217911b50029def354746d 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -331,7 +331,7 @@ impl TabSnapshot { pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { let inlay_point = self.to_inlay_point(point, bias).0; - let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point, bias); + let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point); let fold_point = self .inlay_snapshot .suggestion_snapshot diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index f976fd924befad56415f8f01bac76d3c7d5108f1..3f4d1f202fedc1392dca0d3112db212652d6b976 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -766,7 +766,7 @@ impl WrapSnapshot { let suggestion_point = self .tab_snapshot .inlay_snapshot - .to_suggestion_point(inlay_point, Bias::Left); + .to_suggestion_point(inlay_point); let fold_point = self .tab_snapshot .inlay_snapshot From ab7dd8042316bdfb8d2883edb6a2e217d22761dc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 10:53:26 +0300 Subject: [PATCH 063/169] Add more InlaySnapshot text summary impls Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 62 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index ed58e44432b0c683add3c893dea0873599a7d334..ceba38388b6380fb0823a64c5337955189bfdddf 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -310,7 +310,6 @@ impl InlayMap { drop(cursor); snapshot.suggestion_snapshot = suggestion_snapshot; - dbg!(new_transforms.items(&())); snapshot.transforms = new_transforms; (snapshot.clone(), Default::default()) @@ -555,10 +554,61 @@ impl InlaySnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - // TODO kb copied from suggestion_map - self.suggestion_snapshot.text_summary_for_range( - self.to_suggestion_point(range.start)..self.to_suggestion_point(range.end), - ) + let mut summary = TextSummary::default(); + + let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let overshoot = range.start.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let suggestion_start = cursor.start().1 .0; + let suffix_start = SuggestionPoint(suggestion_start + overshoot); + let suffix_end = SuggestionPoint( + suggestion_start + + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0), + ); + summary = self + .suggestion_snapshot + .text_summary_for_range(suffix_start..suffix_end); + cursor.next(&()); + } + Some(Transform::Inlay(inlay)) => { + let text = &inlay.properties.text; + let suffix_start = text.point_to_offset(overshoot); + let suffix_end = text.point_to_offset( + cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0, + ); + summary = text.cursor(suffix_start).summary(suffix_end); + cursor.next(&()); + } + None => {} + } + + if range.end > cursor.start().0 { + summary += cursor + .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) + .output; + + let overshoot = range.end.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let prefix_start = cursor.start().1; + let prefix_end = SuggestionPoint(prefix_start.0 + overshoot); + summary += self + .suggestion_snapshot + .text_summary_for_range(prefix_start..prefix_end); + } + Some(Transform::Inlay(inlay)) => { + let text = &inlay.properties.text; + let prefix_end = text.point_to_offset(overshoot); + summary += text.cursor(0).summary::(prefix_end); + } + None => {} + } + } + + summary } pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { @@ -579,8 +629,6 @@ impl InlaySnapshot { text_highlights: Option<&'a TextHighlights>, suggestion_highlight: Option, ) -> InlayChunks<'a> { - dbg!(self.transforms.items(&())); - let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); cursor.seek(&range.start, Bias::Right, &()); From 2ba3262f29168f100bac153fd77f4768746f2fe2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 10:56:58 +0300 Subject: [PATCH 064/169] Add line_len snapshot method Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index ceba38388b6380fb0823a64c5337955189bfdddf..529b056eaf2cb59cce376f819df40c6ef6364125 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -549,7 +549,7 @@ impl InlaySnapshot { match bias { Bias::Left => cursor.end(&()), - Bias::Right => *cursor.start(), + Bias::Right => cursor.start(), } } @@ -618,8 +618,13 @@ impl InlaySnapshot { } pub fn line_len(&self, row: u32) -> u32 { - // TODO kb copied from suggestion_map - self.suggestion_snapshot.line_len(row) + let line_start = self.to_offset(InlayPoint::new(row, 0)).0; + let line_end = if row >= self.max_point().row() { + self.len().0 + } else { + self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1 + }; + (line_end - line_start) as u32 } pub fn chunks<'a>( From 4d76162da897c91bef56aaba639fadaa580f8b70 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 11:04:36 +0300 Subject: [PATCH 065/169] Report the edits per transform summary generated Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 26 +++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 529b056eaf2cb59cce376f819df40c6ef6364125..f5c9bbfb4e14f325aa5db16d4a1e8d438010e126 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -297,12 +297,12 @@ impl InlayMap { transform_end += cursor.end(&()) - suggestion_edit.old.end; cursor.next(&()); } - new_transforms.push( - Transform::Isomorphic(suggestion_snapshot.text_summary_for_range( + push_isomorphic( + &mut new_transforms, + suggestion_snapshot.text_summary_for_range( suggestion_snapshot.to_point(transform_start) ..suggestion_snapshot.to_point(transform_end), - )), - &(), + ), ); } @@ -549,7 +549,7 @@ impl InlaySnapshot { match bias { Bias::Left => cursor.end(&()), - Bias::Right => cursor.start(), + Bias::Right => *cursor.start(), } } @@ -664,6 +664,22 @@ impl InlaySnapshot { } } +fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { + let mut summary = Some(summary); + sum_tree.update_last( + |transform| { + if let Transform::Isomorphic(transform) = transform { + *transform += summary.take().unwrap(); + } + }, + &(), + ); + + if let Some(summary) = summary { + sum_tree.push(Transform::Isomorphic(summary), &()); + } +} + #[cfg(test)] mod tests { use super::*; From 2e730d8fa40ba68f67a1e380dd44ff8536dbd98d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 11:18:51 +0300 Subject: [PATCH 066/169] Implement initial changes reporting for inlay hints Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 41 ++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index f5c9bbfb4e14f325aa5db16d4a1e8d438010e126..d2fa17dca92f6379c0777beebb06cd3427748d8d 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -268,17 +268,18 @@ impl InlayMap { ) -> (InlaySnapshot, Vec) { let mut snapshot = self.snapshot.lock(); - if snapshot.suggestion_snapshot.version != suggestion_snapshot.version { - snapshot.version += 1; + let mut new_snapshot = snapshot.clone(); + if new_snapshot.suggestion_snapshot.version != suggestion_snapshot.version { + new_snapshot.version += 1; } - let mut new_transforms = SumTree::new(); + new_snapshot.transforms = SumTree::new(); let mut cursor = snapshot.transforms.cursor::(); let mut suggestion_edits = suggestion_edits.iter().peekable(); while let Some(suggestion_edit) = suggestion_edits.next() { if suggestion_edit.old.start >= *cursor.start() { - new_transforms.push_tree( + new_snapshot.transforms.push_tree( cursor.slice(&suggestion_edit.old.start, Bias::Right, &()), &(), ); @@ -288,7 +289,7 @@ impl InlayMap { cursor.seek_forward(&suggestion_edit.old.end, Bias::Right, &()); } - let transform_start = SuggestionOffset(new_transforms.summary().input.len); + let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); let mut transform_end = suggestion_edit.new.end; if suggestion_edits .peek() @@ -298,7 +299,7 @@ impl InlayMap { cursor.next(&()); } push_isomorphic( - &mut new_transforms, + &mut new_snapshot.transforms, suggestion_snapshot.text_summary_for_range( suggestion_snapshot.to_point(transform_start) ..suggestion_snapshot.to_point(transform_end), @@ -306,13 +307,21 @@ impl InlayMap { ); } - new_transforms.push_tree(cursor.suffix(&()), &()); + new_snapshot.transforms.push_tree(cursor.suffix(&()), &()); + new_snapshot.suggestion_snapshot = suggestion_snapshot; drop(cursor); - snapshot.suggestion_snapshot = suggestion_snapshot; - snapshot.transforms = new_transforms; + let mut inlay_edits = Vec::new(); + for suggestion_edit in suggestion_edits { + let old = snapshot.to_inlay_offset(suggestion_edit.old.start) + ..snapshot.to_inlay_offset(suggestion_edit.old.end); + let new = new_snapshot.to_inlay_offset(suggestion_edit.new.start) + ..new_snapshot.to_inlay_offset(suggestion_edit.new.end); + inlay_edits.push(Edit { old, new }) + } - (snapshot.clone(), Default::default()) + *snapshot = new_snapshot.clone(); + (new_snapshot, inlay_edits) } pub fn splice( @@ -516,6 +525,18 @@ impl InlaySnapshot { } } + pub fn to_inlay_offset(&self, offset: SuggestionOffset) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(SuggestionOffset, InlayOffset)>(); + // TODO kb is the bias right? should we have an external one instead? + cursor.seek(&offset, Bias::Right, &()); + let overshoot = offset.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(transform)) => InlayOffset(cursor.start().1 .0 + overshoot), + Some(Transform::Inlay(inlay)) => cursor.start().1, + None => self.len(), + } + } + pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>(); // TODO kb is the bias right? should we have an external one instead? From f5f495831a59d7aee8304da1c4d6fb51ed57d75b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 12:55:42 +0300 Subject: [PATCH 067/169] Add inlay hints randomized test, fix the errors Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 262 +++++++++++++++++- .../editor/src/display_map/suggestion_map.rs | 1 - 2 files changed, 253 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index d2fa17dca92f6379c0777beebb06cd3427748d8d..66be89b5e48b6d6b105ba7dfaf4b9367041a119e 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -275,9 +275,9 @@ impl InlayMap { new_snapshot.transforms = SumTree::new(); let mut cursor = snapshot.transforms.cursor::(); - let mut suggestion_edits = suggestion_edits.iter().peekable(); + let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); - while let Some(suggestion_edit) = suggestion_edits.next() { + while let Some(suggestion_edit) = suggestion_edits_iter.next() { if suggestion_edit.old.start >= *cursor.start() { new_snapshot.transforms.push_tree( cursor.slice(&suggestion_edit.old.start, Bias::Right, &()), @@ -291,13 +291,14 @@ impl InlayMap { let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); let mut transform_end = suggestion_edit.new.end; - if suggestion_edits + if suggestion_edits_iter .peek() - .map_or(true, |edit| edit.old.start > cursor.end(&())) + .map_or(true, |edit| edit.old.start >= cursor.end(&())) { transform_end += cursor.end(&()) - suggestion_edit.old.end; cursor.next(&()); } + push_isomorphic( &mut new_snapshot.transforms, suggestion_snapshot.text_summary_for_range( @@ -550,12 +551,19 @@ impl InlaySnapshot { } pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { - let mut cursor = self.transforms.cursor::(); + let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); cursor.seek(&point, bias, &()); match cursor.item() { - Some(Transform::Isomorphic(_)) => return point, + Some(Transform::Isomorphic(_)) => { + let overshoot = point.0 - cursor.start().0 .0; + let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot); + let clipped_suggestion_point = + self.suggestion_snapshot.clip_point(suggestion_point, bias); + let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0; + return InlayPoint(cursor.start().0 .0 + clipped_overshoot); + } Some(Transform::Inlay(_)) => {} - None => cursor.prev(&()), + None => return self.max_point(), } while cursor @@ -569,8 +577,8 @@ impl InlaySnapshot { } match bias { - Bias::Left => cursor.end(&()), - Bias::Right => *cursor.start(), + Bias::Left => cursor.end(&()).0, + Bias::Right => cursor.start().0, } } @@ -632,6 +640,7 @@ impl InlaySnapshot { summary } + // TODO kb copied from suggestion_snapshot pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { InlayBufferRows { suggestion_rows: self.suggestion_snapshot.buffer_rows(row), @@ -703,12 +712,17 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { #[cfg(test)] mod tests { + use std::env; + use super::*; use crate::{ display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, MultiBuffer, }; use gpui::AppContext; + use rand::rngs::StdRng; + use settings::SettingsStore; + use text::Patch; #[gpui::test] fn test_basic_inlays(cx: &mut AppContext) { @@ -733,6 +747,62 @@ mod tests { )], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 0)), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 1)), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 2)), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 5)), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right), + InlayPoint::new(0, 9) + ); buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx)); let (fold_snapshot, fold_edits) = fold_map.read( @@ -772,4 +842,178 @@ mod tests { let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); assert_eq!(inlay_snapshot.text(), "XYZabCdefghi"); } + + #[gpui::test(iterations = 100)] + fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) { + init_test(cx); + + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let len = rng.gen_range(0..30); + let buffer = if rng.gen() { + let text = util::RandomCharIter::new(&mut rng) + .take(len) + .collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let mut buffer_snapshot = buffer.read(cx).snapshot(cx); + log::info!("buffer text: {:?}", buffer_snapshot.text()); + + let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + let (inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + + for _ in 0..operations { + let mut suggestion_edits = Patch::default(); + + let mut prev_inlay_text = inlay_snapshot.text(); + let mut buffer_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=59 => { + for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { + fold_snapshot = new_fold_snapshot; + let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_edits = suggestion_edits.compose(edits); + } + } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), + }; + + let (new_fold_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), buffer_edits); + fold_snapshot = new_fold_snapshot; + let (new_suggestion_snapshot, new_suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_snapshot = new_suggestion_snapshot; + suggestion_edits = suggestion_edits.compose(new_suggestion_edits); + let (new_inlay_snapshot, inlay_edits) = + inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner()); + inlay_snapshot = new_inlay_snapshot; + + log::info!("buffer text: {:?}", buffer_snapshot.text()); + log::info!("folds text: {:?}", fold_snapshot.text()); + log::info!("suggestions text: {:?}", suggestion_snapshot.text()); + log::info!("inlay text: {:?}", inlay_snapshot.text()); + + let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); + let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::>(); + assert_eq!(inlay_snapshot.text(), expected_text.to_string()); + for row_start in 0..expected_buffer_rows.len() { + assert_eq!( + inlay_snapshot + .buffer_rows(row_start as u32) + .collect::>(), + &expected_buffer_rows[row_start..], + "incorrect buffer rows starting at {}", + row_start + ); + } + + for _ in 0..5 { + let mut end = rng.gen_range(0..=inlay_snapshot.len().0); + end = expected_text.clip_offset(end, Bias::Right); + let mut start = rng.gen_range(0..=end); + start = expected_text.clip_offset(start, Bias::Right); + + let actual_text = inlay_snapshot + .chunks(InlayOffset(start)..InlayOffset(end), false, None, None) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, + expected_text.slice(start..end).to_string(), + "incorrect text in range {:?}", + start..end + ); + + let start_point = InlayPoint(expected_text.offset_to_point(start)); + let end_point = InlayPoint(expected_text.offset_to_point(end)); + assert_eq!( + inlay_snapshot.text_summary_for_range(start_point..end_point), + expected_text.slice(start..end).summary() + ); + } + + for edit in inlay_edits { + prev_inlay_text.replace_range( + edit.new.start.0..edit.new.start.0 + edit.old_len().0, + &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0], + ); + } + assert_eq!(prev_inlay_text, inlay_snapshot.text()); + + assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); + assert_eq!(expected_text.len(), inlay_snapshot.len().0); + + let mut inlay_point = InlayPoint::default(); + let mut inlay_offset = InlayOffset::default(); + for ch in expected_text.chars() { + assert_eq!( + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "invalid to_offset({:?})", + inlay_point + ); + assert_eq!( + inlay_snapshot.to_point(inlay_offset), + inlay_point, + "invalid to_point({:?})", + inlay_offset + ); + assert_eq!( + inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)), + inlay_snapshot.clip_point(inlay_point, Bias::Right), + "to_suggestion_point({:?}) = {:?}", + inlay_point, + inlay_snapshot.to_suggestion_point(inlay_point), + ); + + let mut bytes = [0; 4]; + for byte in ch.encode_utf8(&mut bytes).as_bytes() { + inlay_offset.0 += 1; + if *byte == b'\n' { + inlay_point.0 += Point::new(1, 0); + } else { + inlay_point.0 += Point::new(0, 1); + } + + let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left); + let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right); + assert!( + clipped_left_point <= clipped_right_point, + "clipped left point {:?} is greater than clipped right point {:?}", + clipped_left_point, + clipped_right_point + ); + assert_eq!( + clipped_left_point.0, + expected_text.clip_point(clipped_left_point.0, Bias::Left) + ); + assert_eq!( + clipped_right_point.0, + expected_text.clip_point(clipped_right_point.0, Bias::Right) + ); + assert!(clipped_left_point <= inlay_snapshot.max_point()); + assert!(clipped_right_point <= inlay_snapshot.max_point()); + } + } + } + } + + fn init_test(cx: &mut AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } } diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 1b188fe2ede5e40dda035acac2d33cc4fa4af14a..b23f172bcaa6d6cf22505bdd9715d88e6f874217 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -56,7 +56,6 @@ impl SuggestionPoint { } } - #[derive(Clone, Debug)] pub struct Suggestion { pub position: T, From 9ce9b738793be54cd8b7f8254d74a7748ed933de Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 13:21:15 +0300 Subject: [PATCH 068/169] Generate edits for inlay hints Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 30 ++++++++++++++++------ crates/editor/src/display_map/inlay_map.rs | 28 +++++++++++++++----- crates/editor/src/editor.rs | 2 +- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5644d63c37b1eb61f6cf6afa311cf5715db5cd8d..9a7178e1954fddd861a52b185ff5d90263b3d90a 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -285,20 +285,29 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn splice_inlay_hints( + pub fn splice_inlays( &mut self, new_hints: &HashMap>, cx: &mut ModelContext, ) { - let multi_buffer = self.buffer.read(cx); - let multi_snapshot = multi_buffer.snapshot(cx); + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.fold_map.read(buffer_snapshot.clone(), edits); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.read(snapshot, edits); - let mut hints_to_add = Vec::new(); + let mut new_inlays = Vec::new(); for (&location, hints) in new_hints { for hint in hints { let hint_anchor = - multi_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); - hints_to_add.push(( + buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); + new_inlays.push(( location, InlayProperties { position: hint_anchor, @@ -308,11 +317,16 @@ impl DisplayMap { } } - self.inlay_map.splice( + let (snapshot, edits, _) = self.inlay_map.splice( // TODO kb this is wrong, calc diffs in the editor instead. self.inlay_map.inlays.keys().copied().collect(), - hints_to_add, + new_inlays, ); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.read(snapshot, edits); } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 66be89b5e48b6d6b105ba7dfaf4b9367041a119e..c2b5dcc0d240b807c8f6932d91a0ee59b529e9cc 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -23,6 +23,7 @@ use parking_lot::Mutex; use project::InlayHint; use rand::Rng; use sum_tree::{Bias, Cursor, SumTree}; +use text::Patch; use util::post_inc; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -372,10 +373,11 @@ impl InlayMap { } } + let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::new(); let mut cursor = snapshot .transforms - .cursor::<(InlayPoint, SuggestionPoint)>(); + .cursor::<(InlayPoint, (SuggestionPoint, InlayOffset))>(); for ((inlay_point, inlay_id), inlay) in inlays { new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &()); while let Some(transform) = cursor.item() { @@ -387,6 +389,11 @@ impl InlayMap { cursor.next(&()); } else { if inlay.id == inlay_id.0 { + let new_start = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: cursor.start().1 .1..cursor.end(&()).1 .1, + new: new_start..new_start, + }); cursor.next(&()); } break; @@ -396,11 +403,14 @@ impl InlayMap { } if let Some(inlay) = inlay { + let new_start = InlayOffset(new_transforms.summary().output.len); + let new_end = InlayOffset(new_start.0 + inlay.properties.text.len()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { let prefix = inlay_point.0 - cursor.start().0 .0; if !prefix.is_zero() { - let prefix_suggestion_start = cursor.start().1; - let prefix_suggestion_end = SuggestionPoint(cursor.start().1 .0 + prefix); + let prefix_suggestion_start = cursor.start().1 .0; + let prefix_suggestion_end = + SuggestionPoint(cursor.start().1 .0 .0 + prefix); new_transforms.push( Transform::Isomorphic( snapshot.suggestion_snapshot.text_summary_for_range( @@ -413,8 +423,8 @@ impl InlayMap { new_transforms.push(Transform::Inlay(inlay), &()); - let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 + prefix); - let suffix_suggestion_end = cursor.end(&()).1; + let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 .0 + prefix); + let suffix_suggestion_end = cursor.end(&()).1 .0; new_transforms.push( Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range( suffix_suggestion_start..suffix_suggestion_end, @@ -426,6 +436,12 @@ impl InlayMap { } else { new_transforms.push(Transform::Inlay(inlay), &()); } + + let old_start = snapshot.to_offset(inlay_point); + inlay_edits.push(Edit { + old: old_start..old_start, + new: new_start..new_end, + }); } } @@ -434,7 +450,7 @@ impl InlayMap { snapshot.transforms = new_transforms; snapshot.version += 1; - (snapshot.clone(), Vec::new(), new_ids) + (snapshot.clone(), inlay_edits.into_inner(), new_ids) } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9560aa707e043bea70820b7f1c2750b6ed174a1f..b73a02d9c39959e05ee94d2224e825e983de1ea5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2702,7 +2702,7 @@ impl Editor { editor .update(&mut cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { - display_map.splice_inlay_hints(&new_hints, cx); + display_map.splice_inlays(&new_hints, cx); }); }) .log_err() From dbd4b335681485a508c581a9ed651e7a2103e775 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 14:30:58 +0300 Subject: [PATCH 069/169] Fix splice edits generation Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 91 ++++++++++++---------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index c2b5dcc0d240b807c8f6932d91a0ee59b529e9cc..f0367f69dff337a9775169150619aa82728e3630 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -352,9 +352,8 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - let inlay_point = snapshot.to_inlay_point(suggestion_point); - inlays.insert((inlay_point, Reverse(inlay.id)), Some(inlay)); + inlays.insert((suggestion_point, Reverse(inlay.id)), Some(inlay)); } for inlay_id in to_remove { @@ -368,8 +367,7 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - let inlay_point = snapshot.to_inlay_point(suggestion_point); - inlays.insert((inlay_point, Reverse(inlay.id)), None); + inlays.insert((suggestion_point, Reverse(inlay.id)), None); } } @@ -377,12 +375,21 @@ impl InlayMap { let mut new_transforms = SumTree::new(); let mut cursor = snapshot .transforms - .cursor::<(InlayPoint, (SuggestionPoint, InlayOffset))>(); - for ((inlay_point, inlay_id), inlay) in inlays { - new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &()); + .cursor::<(SuggestionPoint, (InlayOffset, InlayPoint))>(); + let mut inlays = inlays.into_iter().peekable(); + while let Some(((suggestion_point, inlay_id), inlay)) = inlays.next() { + new_transforms.push_tree(cursor.slice(&suggestion_point, Bias::Left, &()), &()); + while let Some(transform) = cursor.item() { match transform { - Transform::Isomorphic(_) => break, + Transform::Isomorphic(_) => { + if suggestion_point >= cursor.end(&()).0 { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + } else { + break; + } + } Transform::Inlay(inlay) => { if inlay.id > inlay_id.0 { new_transforms.push(transform.clone(), &()); @@ -391,7 +398,7 @@ impl InlayMap { if inlay.id == inlay_id.0 { let new_start = InlayOffset(new_transforms.summary().output.len); inlay_edits.push(Edit { - old: cursor.start().1 .1..cursor.end(&()).1 .1, + old: cursor.start().1 .0..cursor.end(&()).1 .0, new: new_start..new_start, }); cursor.next(&()); @@ -406,42 +413,44 @@ impl InlayMap { let new_start = InlayOffset(new_transforms.summary().output.len); let new_end = InlayOffset(new_start.0 + inlay.properties.text.len()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { - let prefix = inlay_point.0 - cursor.start().0 .0; - if !prefix.is_zero() { - let prefix_suggestion_start = cursor.start().1 .0; - let prefix_suggestion_end = - SuggestionPoint(cursor.start().1 .0 .0 + prefix); - new_transforms.push( - Transform::Isomorphic( - snapshot.suggestion_snapshot.text_summary_for_range( - prefix_suggestion_start..prefix_suggestion_end, - ), - ), - &(), - ); - } + let prefix_suggestion_start = + SuggestionPoint(new_transforms.summary().input.lines); + push_isomorphic( + &mut new_transforms, + snapshot + .suggestion_snapshot + .text_summary_for_range(prefix_suggestion_start..suggestion_point), + ); + let old_start = snapshot.to_offset(InlayPoint( + cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0), + )); + inlay_edits.push(Edit { + old: old_start..old_start, + new: new_start..new_end, + }); new_transforms.push(Transform::Inlay(inlay), &()); - let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 .0 + prefix); - let suffix_suggestion_end = cursor.end(&()).1 .0; - new_transforms.push( - Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range( - suffix_suggestion_start..suffix_suggestion_end, - )), - &(), - ); - - cursor.next(&()); + if inlays.peek().map_or(true, |((suggestion_point, _), _)| { + *suggestion_point >= cursor.end(&()).0 + }) { + let suffix_suggestion_end = cursor.end(&()).0; + push_isomorphic( + &mut new_transforms, + snapshot + .suggestion_snapshot + .text_summary_for_range(suggestion_point..suffix_suggestion_end), + ); + cursor.next(&()); + } } else { + let old_start = cursor.start().1 .0; + inlay_edits.push(Edit { + old: old_start..old_start, + new: new_start..new_end, + }); new_transforms.push(Transform::Inlay(inlay), &()); } - - let old_start = snapshot.to_offset(inlay_point); - inlay_edits.push(Edit { - old: old_start..old_start, - new: new_start..new_end, - }); } } @@ -711,6 +720,10 @@ impl InlaySnapshot { } fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { + if summary.len == 0 { + return; + } + let mut summary = Some(summary); sum_tree.update_last( |transform| { From f940104b6f095999fdf4173c568a84757007c324 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 14:52:44 +0300 Subject: [PATCH 070/169] Add inlay hint randomization in the text Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 11 +-- crates/editor/src/display_map/inlay_map.rs | 99 +++++++++++++++------- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 9a7178e1954fddd861a52b185ff5d90263b3d90a..f7f20e00116959eb233a85bf57b7e51d06ae258d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -307,13 +307,10 @@ impl DisplayMap { for hint in hints { let hint_anchor = buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); - new_inlays.push(( - location, - InlayProperties { - position: hint_anchor, - text: hint.text().trim_end().into(), - }, - )) + new_inlays.push(InlayProperties { + position: hint_anchor, + text: hint.text().trim_end().into(), + }); } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index f0367f69dff337a9775169150619aa82728e3630..e98ae1b1c910100b2c307897efc5f13c59de73b2 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -32,7 +32,7 @@ pub struct InlayId(usize); pub struct InlayMap { snapshot: Mutex, next_inlay_id: usize, - pub(super) inlays: HashMap, + pub(super) inlays: HashMap, } #[derive(Clone)] @@ -212,10 +212,11 @@ impl<'a> Iterator for InlayChunks<'a> { }; if self.output_offset == self.transforms.end(&()).0 { + self.inlay_chunks = None; self.transforms.next(&()); } - Some(chunk) + Some(dbg!(chunk)) } } @@ -329,18 +330,18 @@ impl InlayMap { pub fn splice( &mut self, to_remove: HashSet, - to_insert: Vec<(InlayHintLocation, InlayProperties)>, + to_insert: Vec, ) -> (InlaySnapshot, Vec, Vec) { let mut snapshot = self.snapshot.lock(); let mut inlays = BTreeMap::new(); let mut new_ids = Vec::new(); - for (location, properties) in to_insert { + for properties in to_insert { let inlay = Inlay { id: InlayId(post_inc(&mut self.next_inlay_id)), properties, }; - self.inlays.insert(inlay.id, (location, inlay.clone())); + self.inlays.insert(inlay.id, inlay.clone()); new_ids.push(inlay.id); let buffer_point = inlay @@ -357,7 +358,7 @@ impl InlayMap { } for inlay_id in to_remove { - if let Some((_, inlay)) = self.inlays.remove(&inlay_id) { + if let Some(inlay) = self.inlays.remove(&inlay_id) { let buffer_point = inlay .properties .position @@ -410,17 +411,17 @@ impl InlayMap { } if let Some(inlay) = inlay { + let prefix_suggestion_start = SuggestionPoint(new_transforms.summary().input.lines); + push_isomorphic( + &mut new_transforms, + snapshot + .suggestion_snapshot + .text_summary_for_range(prefix_suggestion_start..suggestion_point), + ); + let new_start = InlayOffset(new_transforms.summary().output.len); let new_end = InlayOffset(new_start.0 + inlay.properties.text.len()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { - let prefix_suggestion_start = - SuggestionPoint(new_transforms.summary().input.lines); - push_isomorphic( - &mut new_transforms, - snapshot - .suggestion_snapshot - .text_summary_for_range(prefix_suggestion_start..suggestion_point), - ); let old_start = snapshot.to_offset(InlayPoint( cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0), )); @@ -461,6 +462,43 @@ impl InlayMap { (snapshot.clone(), inlay_edits.into_inner(), new_ids) } + + #[cfg(test)] + pub fn randomly_mutate( + &mut self, + rng: &mut rand::rngs::StdRng, + ) -> (InlaySnapshot, Vec, Vec) { + use rand::seq::IteratorRandom; + + let mut to_remove = HashSet::default(); + let mut to_insert = Vec::default(); + let snapshot = self.snapshot.lock(); + for _ in 0..rng.gen_range(1..=5) { + if self.inlays.is_empty() || rng.gen() { + let buffer_snapshot = snapshot.buffer_snapshot(); + let position = buffer_snapshot.random_byte_range(0, rng).start; + let len = rng.gen_range(1..=5); + let text = util::RandomCharIter::new(&mut *rng) + .take(len) + .collect::(); + log::info!( + "creating inlay at buffer offset {} with text {:?}", + position, + text + ); + + to_insert.push(InlayProperties { + position: buffer_snapshot.anchor_before(position), + text: text.as_str().into(), + }); + } else { + to_remove.insert(*self.inlays.keys().choose(rng).unwrap()); + } + } + + drop(snapshot); + self.splice(to_remove, to_insert) + } } impl InlaySnapshot { @@ -741,16 +779,15 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { #[cfg(test)] mod tests { - use std::env; - use super::*; use crate::{ display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, MultiBuffer, }; use gpui::AppContext; - use rand::rngs::StdRng; + use rand::prelude::*; use settings::SettingsStore; + use std::env; use text::Patch; #[gpui::test] @@ -764,16 +801,10 @@ mod tests { let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( HashSet::default(), - vec![( - InlayHintLocation { - buffer_id: 0, - excerpt_id: ExcerptId::default(), - }, - InlayProperties { - position: buffer.read(cx).read(cx).anchor_before(3), - text: "|123|".into(), - }, - )], + vec![InlayProperties { + position: buffer.read(cx).read(cx).anchor_before(3), + text: "|123|".into(), + }], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( @@ -894,15 +925,22 @@ mod tests { let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - let (inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); for _ in 0..operations { let mut suggestion_edits = Patch::default(); + let mut inlay_edits = Patch::default(); let mut prev_inlay_text = inlay_snapshot.text(); let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=59 => { + 0..=29 => { + let (snapshot, edits, _) = inlay_map.randomly_mutate(&mut rng); + dbg!(&edits); + inlay_snapshot = snapshot; + inlay_edits = Patch::new(edits); + } + 30..=59 => { for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { fold_snapshot = new_fold_snapshot; let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); @@ -927,9 +965,10 @@ mod tests { suggestion_map.sync(fold_snapshot.clone(), fold_edits); suggestion_snapshot = new_suggestion_snapshot; suggestion_edits = suggestion_edits.compose(new_suggestion_edits); - let (new_inlay_snapshot, inlay_edits) = + let (new_inlay_snapshot, new_inlay_edits) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner()); inlay_snapshot = new_inlay_snapshot; + inlay_edits = inlay_edits.compose(new_inlay_edits); log::info!("buffer text: {:?}", buffer_snapshot.text()); log::info!("folds text: {:?}", fold_snapshot.text()); From afa59eed017113fa66d67885ee5b3cbd902d5f8a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 15:50:54 +0300 Subject: [PATCH 071/169] Fix the randomized tests Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 4 +- crates/editor/src/display_map/block_map.rs | 4 +- crates/editor/src/display_map/inlay_map.rs | 77 ++++++++++++++++------ crates/editor/src/display_map/wrap_map.rs | 2 +- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f7f20e00116959eb233a85bf57b7e51d06ae258d..17b8733b3aee906eaf49dab0cf76acd9a6757760 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -96,7 +96,7 @@ impl DisplayMap { } } - pub fn snapshot(&self, cx: &mut ModelContext) -> DisplaySnapshot { + pub fn snapshot(&mut self, cx: &mut ModelContext) -> DisplaySnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); @@ -249,7 +249,7 @@ impl DisplayMap { } pub fn replace_suggestion( - &self, + &mut self, new_suggestion: Option>, cx: &mut ModelContext, ) -> Option> diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index d2c2d11e1dd2d5ee1bcbdf8b8da38afe7cc1bfe5..85d5275dd312a75ecab27fc04428253dcead5ef1 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1033,7 +1033,7 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1283,7 +1283,7 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e98ae1b1c910100b2c307897efc5f13c59de73b2..2a780ea6b7b4b65c47559dc5e823d92c0fd83ed8 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -184,9 +184,11 @@ impl<'a> Iterator for InlayChunks<'a> { *chunk = self.suggestion_chunks.next().unwrap(); } - let (prefix, suffix) = chunk - .text - .split_at(cmp::min(transform.len, chunk.text.len())); + let (prefix, suffix) = chunk.text.split_at(cmp::min( + self.transforms.end(&()).0 .0 - self.output_offset.0, + chunk.text.len(), + )); + chunk.text = suffix; self.output_offset.0 += prefix.len(); Chunk { @@ -216,7 +218,7 @@ impl<'a> Iterator for InlayChunks<'a> { self.transforms.next(&()); } - Some(dbg!(chunk)) + Some(chunk) } } @@ -264,7 +266,7 @@ impl InlayMap { } pub fn sync( - &self, + &mut self, suggestion_snapshot: SuggestionSnapshot, suggestion_edits: Vec, ) -> (InlaySnapshot, Vec) { @@ -280,15 +282,19 @@ impl InlayMap { let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); while let Some(suggestion_edit) = suggestion_edits_iter.next() { + if suggestion_edit.old.start >= *cursor.start() { if suggestion_edit.old.start >= *cursor.start() { new_snapshot.transforms.push_tree( - cursor.slice(&suggestion_edit.old.start, Bias::Right, &()), + cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), &(), ); } - if suggestion_edit.old.end > cursor.end(&()) { - cursor.seek_forward(&suggestion_edit.old.end, Bias::Right, &()); + while suggestion_edit.old.end > cursor.end(&()) { + if let Some(Transform::Inlay(inlay)) = cursor.item() { + self.inlays.remove(&inlay.id); + } + cursor.next(&()); } let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); @@ -324,6 +330,7 @@ impl InlayMap { } *snapshot = new_snapshot.clone(); + snapshot.check_invariants(); (new_snapshot, inlay_edits) } @@ -459,6 +466,7 @@ impl InlayMap { drop(cursor); snapshot.transforms = new_transforms; snapshot.version += 1; + snapshot.check_invariants(); (snapshot.clone(), inlay_edits.into_inner(), new_ids) } @@ -488,7 +496,7 @@ impl InlayMap { ); to_insert.push(InlayProperties { - position: buffer_snapshot.anchor_before(position), + position: buffer_snapshot.anchor_after(position), text: text.as_str().into(), }); } else { @@ -755,6 +763,16 @@ impl InlaySnapshot { .map(|chunk| chunk.text) .collect() } + + fn check_invariants(&self) { + #[cfg(any(debug_assertions, feature = "test-support"))] + { + assert_eq!( + self.transforms.summary().input, + self.suggestion_snapshot.text_summary() + ); + } + } } fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { @@ -936,7 +954,6 @@ mod tests { match rng.gen_range(0..=100) { 0..=29 => { let (snapshot, edits, _) = inlay_map.randomly_mutate(&mut rng); - dbg!(&edits); inlay_snapshot = snapshot; inlay_edits = Patch::new(edits); } @@ -975,19 +992,37 @@ mod tests { log::info!("suggestions text: {:?}", suggestion_snapshot.text()); log::info!("inlay text: {:?}", inlay_snapshot.text()); + let mut inlays = inlay_map + .inlays + .values() + .map(|inlay| { + let buffer_point = inlay.properties.position.to_point(&buffer_snapshot); + let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); + let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point); + let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point); + (suggestion_offset, inlay.clone()) + }) + .collect::>(); + inlays.sort_by_key(|(offset, inlay)| (*offset, Reverse(inlay.id))); let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); - let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::>(); - assert_eq!(inlay_snapshot.text(), expected_text.to_string()); - for row_start in 0..expected_buffer_rows.len() { - assert_eq!( - inlay_snapshot - .buffer_rows(row_start as u32) - .collect::>(), - &expected_buffer_rows[row_start..], - "incorrect buffer rows starting at {}", - row_start - ); + for (offset, inlay) in inlays.into_iter().rev() { + expected_text.replace(offset.0..offset.0, &inlay.properties.text.to_string()); } + assert_eq!(inlay_snapshot.text(), expected_text.to_string()); + continue; + + // let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::>(); + + // for row_start in 0..expected_buffer_rows.len() { + // assert_eq!( + // inlay_snapshot + // .buffer_rows(row_start as u32) + // .collect::>(), + // &expected_buffer_rows[row_start..], + // "incorrect buffer rows starting at {}", + // row_start + // ); + // } for _ in 0..5 { let mut end = rng.gen_range(0..=inlay_snapshot.len().0); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 3f4d1f202fedc1392dca0d3112db212652d6b976..a18cc7da56b728c53f6e79f0dcd4797e969a571e 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1102,7 +1102,7 @@ mod tests { log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); log::info!("InlaysMap text: {:?}", inlay_snapshot.text()); let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); From e280483c5f17545437398f9095df7c2dee43154b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 9 Jun 2023 16:35:17 +0200 Subject: [PATCH 072/169] Make the randomized tests pass Right now we only check that the text is correct, but I think we're getting there. --- crates/editor/src/display_map/inlay_map.rs | 54 ++++++++++++++++------ 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 2a780ea6b7b4b65c47559dc5e823d92c0fd83ed8..a79bd69786d281d6030af0b784d213fc5cf7794a 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -282,14 +282,20 @@ impl InlayMap { let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); while let Some(suggestion_edit) = suggestion_edits_iter.next() { - if suggestion_edit.old.start >= *cursor.start() { - if suggestion_edit.old.start >= *cursor.start() { - new_snapshot.transforms.push_tree( - cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), - &(), - ); + new_snapshot.transforms.push_tree( + cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), + &(), + ); + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if cursor.end(&()) == suggestion_edit.old.start { + new_snapshot + .transforms + .push(Transform::Isomorphic(transform.clone()), &()); + cursor.next(&()); + } } + // Remove all the inlays and transforms contained by the edit. while suggestion_edit.old.end > cursor.end(&()) { if let Some(Transform::Inlay(inlay)) = cursor.item() { self.inlays.remove(&inlay.id); @@ -297,16 +303,9 @@ impl InlayMap { cursor.next(&()); } + // Apply the edit. let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); let mut transform_end = suggestion_edit.new.end; - if suggestion_edits_iter - .peek() - .map_or(true, |edit| edit.old.start >= cursor.end(&())) - { - transform_end += cursor.end(&()) - suggestion_edit.old.end; - cursor.next(&()); - } - push_isomorphic( &mut new_snapshot.transforms, suggestion_snapshot.text_summary_for_range( @@ -314,6 +313,33 @@ impl InlayMap { ..suggestion_snapshot.to_point(transform_end), ), ); + + // Push all the inlays starting at the end of the edit. + while let Some(Transform::Inlay(inlay)) = cursor.item() { + new_snapshot + .transforms + .push(Transform::Inlay(inlay.clone()), &()); + cursor.next(&()); + } + + // If the next edit doesn't intersect the current isomorphic transform, then + // we can push its remainder. + if suggestion_edits_iter + .peek() + .map_or(true, |edit| edit.old.start >= cursor.end(&())) + { + let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); + let transform_end = + suggestion_edit.new.end + (cursor.end(&()) - suggestion_edit.old.end); + push_isomorphic( + &mut new_snapshot.transforms, + suggestion_snapshot.text_summary_for_range( + suggestion_snapshot.to_point(transform_start) + ..suggestion_snapshot.to_point(transform_end), + ), + ); + cursor.next(&()); + } } new_snapshot.transforms.push_tree(cursor.suffix(&()), &()); From 8a64b07622cf6ecfeb674db5f5c1373443c8a9c4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 18:12:10 +0300 Subject: [PATCH 073/169] Fixed inlay hints' edits generation and moved on with the randomized test Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 61 ++++++++++++++-------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index a79bd69786d281d6030af0b784d213fc5cf7794a..bf9a466f0d9cddd61bf88d5c42b75251230f2370 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -277,17 +277,19 @@ impl InlayMap { new_snapshot.version += 1; } + let mut inlay_edits = Patch::default(); new_snapshot.transforms = SumTree::new(); - let mut cursor = snapshot.transforms.cursor::(); + let mut cursor = snapshot + .transforms + .cursor::<(SuggestionOffset, InlayOffset)>(); let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); - while let Some(suggestion_edit) = suggestion_edits_iter.next() { new_snapshot.transforms.push_tree( cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), &(), ); if let Some(Transform::Isomorphic(transform)) = cursor.item() { - if cursor.end(&()) == suggestion_edit.old.start { + if cursor.end(&()).0 == suggestion_edit.old.start { new_snapshot .transforms .push(Transform::Isomorphic(transform.clone()), &()); @@ -296,21 +298,43 @@ impl InlayMap { } // Remove all the inlays and transforms contained by the edit. - while suggestion_edit.old.end > cursor.end(&()) { + let old_start = + cursor.start().1 + InlayOffset(suggestion_edit.old.start.0 - cursor.start().0 .0); + while suggestion_edit.old.end > cursor.end(&()).0 { if let Some(Transform::Inlay(inlay)) = cursor.item() { self.inlays.remove(&inlay.id); } cursor.next(&()); } + let old_end = + cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0); + + // Push the unchanged prefix. + let prefix_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); + let prefix_end = suggestion_edit.new.start; + push_isomorphic( + &mut new_snapshot.transforms, + suggestion_snapshot.text_summary_for_range( + suggestion_snapshot.to_point(prefix_start) + ..suggestion_snapshot.to_point(prefix_end), + ), + ); + + let new_start = InlayOffset(new_snapshot.transforms.summary().output.len); + let new_end = InlayOffset( + new_snapshot.transforms.summary().output.len + suggestion_edit.new_len().0, + ); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); // Apply the edit. - let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); - let mut transform_end = suggestion_edit.new.end; push_isomorphic( &mut new_snapshot.transforms, suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(transform_start) - ..suggestion_snapshot.to_point(transform_end), + suggestion_snapshot.to_point(suggestion_edit.new.start) + ..suggestion_snapshot.to_point(suggestion_edit.new.end), ), ); @@ -326,11 +350,11 @@ impl InlayMap { // we can push its remainder. if suggestion_edits_iter .peek() - .map_or(true, |edit| edit.old.start >= cursor.end(&())) + .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) { let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); let transform_end = - suggestion_edit.new.end + (cursor.end(&()) - suggestion_edit.old.end); + suggestion_edit.new.end + (cursor.end(&()).0 - suggestion_edit.old.end); push_isomorphic( &mut new_snapshot.transforms, suggestion_snapshot.text_summary_for_range( @@ -346,18 +370,9 @@ impl InlayMap { new_snapshot.suggestion_snapshot = suggestion_snapshot; drop(cursor); - let mut inlay_edits = Vec::new(); - for suggestion_edit in suggestion_edits { - let old = snapshot.to_inlay_offset(suggestion_edit.old.start) - ..snapshot.to_inlay_offset(suggestion_edit.old.end); - let new = new_snapshot.to_inlay_offset(suggestion_edit.new.start) - ..new_snapshot.to_inlay_offset(suggestion_edit.new.end); - inlay_edits.push(Edit { old, new }) - } - *snapshot = new_snapshot.clone(); snapshot.check_invariants(); - (new_snapshot, inlay_edits) + (new_snapshot, inlay_edits.into_inner()) } pub fn splice( @@ -647,6 +662,7 @@ impl InlaySnapshot { } } + // TODO kb clippig is funky, does not allow to get to left pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); cursor.seek(&point, bias, &()); @@ -1035,10 +1051,8 @@ mod tests { expected_text.replace(offset.0..offset.0, &inlay.properties.text.to_string()); } assert_eq!(inlay_snapshot.text(), expected_text.to_string()); - continue; - + // TODO kb !!! // let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::>(); - // for row_start in 0..expected_buffer_rows.len() { // assert_eq!( // inlay_snapshot @@ -1085,6 +1099,7 @@ mod tests { assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); assert_eq!(expected_text.len(), inlay_snapshot.len().0); + continue; // TODO kb fix the rest of the test let mut inlay_point = InlayPoint::default(); let mut inlay_offset = InlayOffset::default(); From df20a4370496cb16ec90d1ddc4fe6f8e45dbe10c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 18:20:55 +0300 Subject: [PATCH 074/169] Reuse the copilot suggestion style for inlays --- crates/editor/src/display_map/inlay_map.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index bf9a466f0d9cddd61bf88d5c42b75251230f2370..e64f74005206e7e1588280d81c2f90a8ce08bf6a 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -153,6 +153,7 @@ pub struct InlayChunks<'a> { inlay_chunks: Option>, output_offset: InlayOffset, max_output_offset: InlayOffset, + highlight_style: Option, } #[derive(Debug, Clone)] @@ -208,6 +209,7 @@ impl<'a> Iterator for InlayChunks<'a> { self.output_offset.0 += chunk.len(); Chunk { text: chunk, + highlight_style: self.highlight_style, ..Default::default() } } @@ -796,6 +798,7 @@ impl InlaySnapshot { suggestion_chunk: None, output_offset: range.start, max_output_offset: range.end, + highlight_style: suggestion_highlight, } } From c7fa8dbc70daa291de42a579d6102da3aaa5988d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 9 Jun 2023 18:28:01 +0300 Subject: [PATCH 075/169] React with inlay updates on excerpt events --- crates/editor/src/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b73a02d9c39959e05ee94d2224e825e983de1ea5..2d5224de32a3794860653739cd683c0ceee985de 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7250,11 +7250,11 @@ impl Editor { predecessor: *predecessor, excerpts: excerpts.clone(), }); - false + true } multi_buffer::Event::ExcerptsRemoved { ids } => { cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); - false + true } multi_buffer::Event::Reparsed => { cx.emit(Event::Reparsed); From 7684a26daaaa29ff0176da08b20265980eecdbf4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 10 Jun 2023 19:35:47 +0200 Subject: [PATCH 076/169] Fix point/offset translation and clipping in the `InlayMap` This makes all randomized tests pass. We're only missing `buffer_rows` now and we should move the map right above `MultiBuffer` and below `FoldMap`. --- crates/editor/src/display_map/inlay_map.rs | 222 +++++++++------------ 1 file changed, 92 insertions(+), 130 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e64f74005206e7e1588280d81c2f90a8ce08bf6a..19010390c9d22431e4faf463a9236312951e92bf 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,14 +1,3 @@ -#![allow(unused)] -// TODO kb - -use std::{ - cmp::{self, Reverse}, - ops::{Add, AddAssign, Range, Sub}, - sync::atomic::{self, AtomicUsize}, -}; - -use crate::{Anchor, ExcerptId, InlayHintLocation, MultiBufferSnapshot, ToOffset, ToPoint}; - use super::{ suggestion_map::{ SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint, @@ -16,12 +5,15 @@ use super::{ }, TextHighlights, }; +use crate::{Anchor, MultiBufferSnapshot, ToPoint}; use collections::{BTreeMap, HashMap, HashSet}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; -use project::InlayHint; -use rand::Rng; +use std::{ + cmp::{self, Reverse}, + ops::{Add, AddAssign, Range, Sub}, +}; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; use util::post_inc; @@ -49,12 +41,6 @@ enum Transform { Inlay(Inlay), } -impl Transform { - fn is_inlay(&self) -> bool { - matches!(self, Self::Inlay(_)) - } -} - impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -177,7 +163,7 @@ impl<'a> Iterator for InlayChunks<'a> { } let chunk = match self.transforms.item()? { - Transform::Isomorphic(transform) => { + Transform::Isomorphic(_) => { let chunk = self .suggestion_chunk .get_or_insert_with(|| self.suggestion_chunks.next().unwrap()); @@ -280,21 +266,19 @@ impl InlayMap { } let mut inlay_edits = Patch::default(); - new_snapshot.transforms = SumTree::new(); + let mut new_transforms = SumTree::new(); let mut cursor = snapshot .transforms .cursor::<(SuggestionOffset, InlayOffset)>(); let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); while let Some(suggestion_edit) = suggestion_edits_iter.next() { - new_snapshot.transforms.push_tree( + new_transforms.push_tree( cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), &(), ); if let Some(Transform::Isomorphic(transform)) = cursor.item() { if cursor.end(&()).0 == suggestion_edit.old.start { - new_snapshot - .transforms - .push(Transform::Isomorphic(transform.clone()), &()); + new_transforms.push(Transform::Isomorphic(transform.clone()), &()); cursor.next(&()); } } @@ -312,28 +296,26 @@ impl InlayMap { cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0); // Push the unchanged prefix. - let prefix_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); + let prefix_start = SuggestionOffset(new_transforms.summary().input.len); let prefix_end = suggestion_edit.new.start; push_isomorphic( - &mut new_snapshot.transforms, + &mut new_transforms, suggestion_snapshot.text_summary_for_range( suggestion_snapshot.to_point(prefix_start) ..suggestion_snapshot.to_point(prefix_end), ), ); - let new_start = InlayOffset(new_snapshot.transforms.summary().output.len); - let new_end = InlayOffset( - new_snapshot.transforms.summary().output.len + suggestion_edit.new_len().0, - ); + // Apply the edit. + let new_start = InlayOffset(new_transforms.summary().output.len); + let new_end = + InlayOffset(new_transforms.summary().output.len + suggestion_edit.new_len().0); inlay_edits.push(Edit { old: old_start..old_end, new: new_start..new_end, }); - - // Apply the edit. push_isomorphic( - &mut new_snapshot.transforms, + &mut new_transforms, suggestion_snapshot.text_summary_for_range( suggestion_snapshot.to_point(suggestion_edit.new.start) ..suggestion_snapshot.to_point(suggestion_edit.new.end), @@ -342,9 +324,7 @@ impl InlayMap { // Push all the inlays starting at the end of the edit. while let Some(Transform::Inlay(inlay)) = cursor.item() { - new_snapshot - .transforms - .push(Transform::Inlay(inlay.clone()), &()); + new_transforms.push(Transform::Inlay(inlay.clone()), &()); cursor.next(&()); } @@ -354,11 +334,11 @@ impl InlayMap { .peek() .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) { - let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); + let transform_start = SuggestionOffset(new_transforms.summary().input.len); let transform_end = suggestion_edit.new.end + (cursor.end(&()).0 - suggestion_edit.old.end); push_isomorphic( - &mut new_snapshot.transforms, + &mut new_transforms, suggestion_snapshot.text_summary_for_range( suggestion_snapshot.to_point(transform_start) ..suggestion_snapshot.to_point(transform_end), @@ -368,12 +348,13 @@ impl InlayMap { } } - new_snapshot.transforms.push_tree(cursor.suffix(&()), &()); + new_transforms.push_tree(cursor.suffix(&()), &()); + new_snapshot.transforms = new_transforms; new_snapshot.suggestion_snapshot = suggestion_snapshot; + new_snapshot.check_invariants(); drop(cursor); *snapshot = new_snapshot.clone(); - snapshot.check_invariants(); (new_snapshot, inlay_edits.into_inner()) } @@ -471,7 +452,7 @@ impl InlayMap { let new_start = InlayOffset(new_transforms.summary().output.len); let new_end = InlayOffset(new_start.0 + inlay.properties.text.len()); - if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if let Some(Transform::Isomorphic(_)) = cursor.item() { let old_start = snapshot.to_offset(InlayPoint( cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0), )); @@ -514,12 +495,12 @@ impl InlayMap { (snapshot.clone(), inlay_edits.into_inner(), new_ids) } - #[cfg(test)] + #[cfg(any(test, feature = "test-support"))] pub fn randomly_mutate( &mut self, rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec, Vec) { - use rand::seq::IteratorRandom; + use rand::prelude::*; let mut to_remove = HashSet::default(); let mut to_insert = Vec::default(); @@ -564,7 +545,7 @@ impl InlaySnapshot { cursor.seek(&offset, Bias::Right, &()); let overshoot = offset.0 - cursor.start().0 .0; match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { let suggestion_offset_start = cursor.start().1 .1; let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot); let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start); @@ -594,7 +575,7 @@ impl InlaySnapshot { cursor.seek(&point, Bias::Right, &()); let overshoot = point.0 - cursor.start().0 .0; match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { let suggestion_point_start = cursor.start().1 .1; let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot); let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start); @@ -617,12 +598,12 @@ impl InlaySnapshot { pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); cursor.seek(&point, Bias::Right, &()); - let overshoot = point.0 - cursor.start().0 .0; match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { + let overshoot = point.0 - cursor.start().0 .0; SuggestionPoint(cursor.start().1 .0 + overshoot) } - Some(Transform::Inlay(inlay)) => cursor.start().1, + Some(Transform::Inlay(_)) => cursor.start().1, None => self.suggestion_snapshot.max_point(), } } @@ -631,69 +612,69 @@ impl InlaySnapshot { let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); cursor.seek(&offset, Bias::Right, &()); match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { let overshoot = offset - cursor.start().0; cursor.start().1 + SuggestionOffset(overshoot.0) } - Some(Transform::Inlay(inlay)) => cursor.start().1, + Some(Transform::Inlay(_)) => cursor.start().1, None => self.suggestion_snapshot.len(), } } - pub fn to_inlay_offset(&self, offset: SuggestionOffset) -> InlayOffset { - let mut cursor = self.transforms.cursor::<(SuggestionOffset, InlayOffset)>(); - // TODO kb is the bias right? should we have an external one instead? - cursor.seek(&offset, Bias::Right, &()); - let overshoot = offset.0 - cursor.start().0 .0; - match cursor.item() { - Some(Transform::Isomorphic(transform)) => InlayOffset(cursor.start().1 .0 + overshoot), - Some(Transform::Inlay(inlay)) => cursor.start().1, - None => self.len(), - } - } - pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>(); - // TODO kb is the bias right? should we have an external one instead? - cursor.seek(&point, Bias::Right, &()); - let overshoot = point.0 - cursor.start().0 .0; + cursor.seek(&point, Bias::Left, &()); match cursor.item() { - Some(Transform::Isomorphic(transform)) => InlayPoint(cursor.start().1 .0 + overshoot), - Some(Transform::Inlay(inlay)) => cursor.start().1, + Some(Transform::Isomorphic(_)) => { + let overshoot = point.0 - cursor.start().0 .0; + InlayPoint(cursor.start().1 .0 + overshoot) + } + Some(Transform::Inlay(_)) => cursor.start().1, None => self.max_point(), } } - // TODO kb clippig is funky, does not allow to get to left pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); - cursor.seek(&point, bias, &()); - match cursor.item() { - Some(Transform::Isomorphic(_)) => { - let overshoot = point.0 - cursor.start().0 .0; - let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot); - let clipped_suggestion_point = - self.suggestion_snapshot.clip_point(suggestion_point, bias); - let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0; - return InlayPoint(cursor.start().0 .0 + clipped_overshoot); - } - Some(Transform::Inlay(_)) => {} - None => return self.max_point(), - } + cursor.seek(&point, Bias::Left, &()); - while cursor - .item() - .map_or(false, |transform| transform.is_inlay()) - { - match bias { - Bias::Left => cursor.prev(&()), - Bias::Right => cursor.next(&()), + let mut bias = bias; + let mut skipped_inlay = false; + loop { + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + let overshoot = if skipped_inlay { + match bias { + Bias::Left => transform.lines, + Bias::Right => { + if transform.first_line_chars == 0 { + Point::new(1, 0) + } else { + Point::new(0, 1) + } + } + } + } else { + point.0 - cursor.start().0 .0 + }; + let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot); + let clipped_suggestion_point = + self.suggestion_snapshot.clip_point(suggestion_point, bias); + let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0; + return InlayPoint(cursor.start().0 .0 + clipped_overshoot); + } + Some(Transform::Inlay(_)) => skipped_inlay = true, + None => match bias { + Bias::Left => return Default::default(), + Bias::Right => bias = Bias::Left, + }, } - } - match bias { - Bias::Left => cursor.end(&()).0, - Bias::Right => cursor.start().0, + if bias == Bias::Left { + cursor.prev(&()); + } else { + cursor.next(&()); + } } } @@ -705,7 +686,7 @@ impl InlaySnapshot { let overshoot = range.start.0 - cursor.start().0 .0; match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { let suggestion_start = cursor.start().1 .0; let suffix_start = SuggestionPoint(suggestion_start + overshoot); let suffix_end = SuggestionPoint( @@ -736,7 +717,7 @@ impl InlaySnapshot { let overshoot = range.end.0 - cursor.start().0 .0; match cursor.item() { - Some(Transform::Isomorphic(transform)) => { + Some(Transform::Isomorphic(_)) => { let prefix_start = cursor.start().1; let prefix_end = SuggestionPoint(prefix_start.0 + overshoot); summary += self @@ -857,7 +838,7 @@ mod tests { fn test_basic_inlays(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abcdefghi", cx); let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); + let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abcdefghi"); @@ -884,7 +865,7 @@ mod tests { ); assert_eq!( inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)), - InlayPoint::new(0, 8) + InlayPoint::new(0, 3) ); assert_eq!( inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)), @@ -908,7 +889,7 @@ mod tests { ); assert_eq!( inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), - InlayPoint::new(0, 8) + InlayPoint::new(0, 3) ); assert_eq!( inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), @@ -916,18 +897,13 @@ mod tests { ); assert_eq!( inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), - InlayPoint::new(0, 8) - ); - assert_eq!( - inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left), - InlayPoint::new(0, 9) - ); - assert_eq!( - inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right), InlayPoint::new(0, 9) ); - buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx)); + // Edits before or after the inlay should not affect it. + buffer.update(cx, |buffer, cx| { + buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx) + }); let (fold_snapshot, fold_edits) = fold_map.read( buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), @@ -935,27 +911,10 @@ mod tests { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); - assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); + assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); - //////// case: folding and unfolding the text should hine and then return the hint back - let (mut fold_map_writer, _, _) = fold_map.write( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); - let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); - assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi"); - - let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); - assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi"); - - ////////// case: replacing the anchor that got the hint: it should disappear - buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx)); + // An edit surrounding the inlay should invalidate it. + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx)); let (fold_snapshot, fold_edits) = fold_map.read( buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), @@ -963,7 +922,7 @@ mod tests { let (suggestion_snapshot, suggestion_edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); - assert_eq!(inlay_snapshot.text(), "XYZabCdefghi"); + assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); } #[gpui::test(iterations = 100)] @@ -1102,7 +1061,6 @@ mod tests { assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); assert_eq!(expected_text.len(), inlay_snapshot.len().0); - continue; // TODO kb fix the rest of the test let mut inlay_point = InlayPoint::default(); let mut inlay_offset = InlayOffset::default(); @@ -1121,7 +1079,7 @@ mod tests { ); assert_eq!( inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)), - inlay_snapshot.clip_point(inlay_point, Bias::Right), + inlay_snapshot.clip_point(inlay_point, Bias::Left), "to_suggestion_point({:?}) = {:?}", inlay_point, inlay_snapshot.to_suggestion_point(inlay_point), @@ -1144,6 +1102,8 @@ mod tests { clipped_left_point, clipped_right_point ); + + // Ensure the clipped points are at valid text locations. assert_eq!( clipped_left_point.0, expected_text.clip_point(clipped_left_point.0, Bias::Left) @@ -1152,6 +1112,8 @@ mod tests { clipped_right_point.0, expected_text.clip_point(clipped_right_point.0, Bias::Right) ); + + // Ensure the clipped points never overshoot the end of the map. assert!(clipped_left_point <= inlay_snapshot.max_point()); assert!(clipped_right_point <= inlay_snapshot.max_point()); } From 2b1b1225f512c5986e1d951430ee2771384f9645 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 10 Jun 2023 19:47:39 +0200 Subject: [PATCH 077/169] Simplify `InlayMap::splice` interface --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/display_map/inlay_map.rs | 64 +++++++++------------- crates/rope/src/rope.rs | 6 ++ 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 17b8733b3aee906eaf49dab0cf76acd9a6757760..82cef10027832ee828aab598cb4a045f69051c90 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -309,7 +309,7 @@ impl DisplayMap { buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); new_inlays.push(InlayProperties { position: hint_anchor, - text: hint.text().trim_end().into(), + text: hint.text(), }); } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 19010390c9d22431e4faf463a9236312951e92bf..63edddbf8dc1c804d1d5883e0b45d2550ff82e23 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -5,7 +5,7 @@ use super::{ }, TextHighlights, }; -use crate::{Anchor, MultiBufferSnapshot, ToPoint}; +use crate::{Anchor, MultiBufferSnapshot, ToOffset, ToPoint}; use collections::{BTreeMap, HashMap, HashSet}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; @@ -52,7 +52,7 @@ impl sum_tree::Item for Transform { }, Transform::Inlay(inlay) => TransformSummary { input: TextSummary::default(), - output: inlay.properties.text.summary(), + output: inlay.text.summary(), }, } } @@ -145,13 +145,14 @@ pub struct InlayChunks<'a> { #[derive(Debug, Clone)] pub struct Inlay { pub(super) id: InlayId, - pub(super) properties: InlayProperties, + pub(super) position: Anchor, + pub(super) text: Rope, } #[derive(Debug, Clone)] -pub struct InlayProperties { - pub(super) position: Anchor, - pub(super) text: Rope, +pub struct InlayProperties { + pub position: P, + pub text: T, } impl<'a> Iterator for InlayChunks<'a> { @@ -188,7 +189,7 @@ impl<'a> Iterator for InlayChunks<'a> { let start = self.output_offset - self.transforms.start().0; let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0) - self.transforms.start().0; - inlay.properties.text.chunks_in_range(start.0..end.0) + inlay.text.chunks_in_range(start.0..end.0) }); let chunk = inlay_chunks.next().unwrap(); @@ -358,10 +359,10 @@ impl InlayMap { (new_snapshot, inlay_edits.into_inner()) } - pub fn splice( + pub fn splice>( &mut self, to_remove: HashSet, - to_insert: Vec, + to_insert: Vec>, ) -> (InlaySnapshot, Vec, Vec) { let mut snapshot = self.snapshot.lock(); @@ -370,15 +371,13 @@ impl InlayMap { for properties in to_insert { let inlay = Inlay { id: InlayId(post_inc(&mut self.next_inlay_id)), - properties, + position: snapshot.buffer_snapshot().anchor_after(properties.position), + text: properties.text.into(), }; self.inlays.insert(inlay.id, inlay.clone()); new_ids.push(inlay.id); - let buffer_point = inlay - .properties - .position - .to_point(snapshot.buffer_snapshot()); + let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot .suggestion_snapshot .fold_snapshot @@ -390,10 +389,7 @@ impl InlayMap { for inlay_id in to_remove { if let Some(inlay) = self.inlays.remove(&inlay_id) { - let buffer_point = inlay - .properties - .position - .to_point(snapshot.buffer_snapshot()); + let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot .suggestion_snapshot .fold_snapshot @@ -451,7 +447,7 @@ impl InlayMap { ); let new_start = InlayOffset(new_transforms.summary().output.len); - let new_end = InlayOffset(new_start.0 + inlay.properties.text.len()); + let new_end = InlayOffset(new_start.0 + inlay.text.len()); if let Some(Transform::Isomorphic(_)) = cursor.item() { let old_start = snapshot.to_offset(InlayPoint( cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0), @@ -518,11 +514,7 @@ impl InlayMap { position, text ); - - to_insert.push(InlayProperties { - position: buffer_snapshot.anchor_after(position), - text: text.as_str().into(), - }); + to_insert.push(InlayProperties { position, text }); } else { to_remove.insert(*self.inlays.keys().choose(rng).unwrap()); } @@ -553,7 +545,7 @@ impl InlaySnapshot { InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) } Some(Transform::Inlay(inlay)) => { - let overshoot = inlay.properties.text.offset_to_point(overshoot); + let overshoot = inlay.text.offset_to_point(overshoot); InlayPoint(cursor.start().1 .0 .0 + overshoot) } None => self.max_point(), @@ -583,7 +575,7 @@ impl InlaySnapshot { InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) } Some(Transform::Inlay(inlay)) => { - let overshoot = inlay.properties.text.point_to_offset(overshoot); + let overshoot = inlay.text.point_to_offset(overshoot); InlayOffset(cursor.start().1 .0 .0 + overshoot) } None => self.len(), @@ -699,12 +691,11 @@ impl InlaySnapshot { cursor.next(&()); } Some(Transform::Inlay(inlay)) => { - let text = &inlay.properties.text; - let suffix_start = text.point_to_offset(overshoot); - let suffix_end = text.point_to_offset( + let suffix_start = inlay.text.point_to_offset(overshoot); + let suffix_end = inlay.text.point_to_offset( cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0, ); - summary = text.cursor(suffix_start).summary(suffix_end); + summary = inlay.text.cursor(suffix_start).summary(suffix_end); cursor.next(&()); } None => {} @@ -725,9 +716,8 @@ impl InlaySnapshot { .text_summary_for_range(prefix_start..prefix_end); } Some(Transform::Inlay(inlay)) => { - let text = &inlay.properties.text; - let prefix_end = text.point_to_offset(overshoot); - summary += text.cursor(0).summary::(prefix_end); + let prefix_end = inlay.text.point_to_offset(overshoot); + summary += inlay.text.cursor(0).summary::(prefix_end); } None => {} } @@ -846,8 +836,8 @@ mod tests { let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( HashSet::default(), vec![InlayProperties { - position: buffer.read(cx).read(cx).anchor_before(3), - text: "|123|".into(), + position: 3, + text: "|123|", }], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); @@ -1000,7 +990,7 @@ mod tests { .inlays .values() .map(|inlay| { - let buffer_point = inlay.properties.position.to_point(&buffer_snapshot); + let buffer_point = inlay.position.to_point(&buffer_snapshot); let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point); let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point); @@ -1010,7 +1000,7 @@ mod tests { inlays.sort_by_key(|(offset, inlay)| (*offset, Reverse(inlay.id))); let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); for (offset, inlay) in inlays.into_iter().rev() { - expected_text.replace(offset.0..offset.0, &inlay.properties.text.to_string()); + expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); } assert_eq!(inlay_snapshot.text(), expected_text.to_string()); // TODO kb !!! diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 0b76dba319fce58db77a4ea8a0c1e6c1921ecb95..2bfb090bb204fce393b7ac816e1000413228c056 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -384,6 +384,12 @@ impl<'a> From<&'a str> for Rope { } } +impl From for Rope { + fn from(text: String) -> Self { + Rope::from(text.as_str()) + } +} + impl fmt::Display for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for chunk in self.chunks() { From 63074c5cd8765f6b5c8c03025610f6355f696dcd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 12 Jun 2023 11:32:41 +0300 Subject: [PATCH 078/169] Better bias selection for hints that prefix the type Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 4 +- crates/editor/src/display_map/inlay_map.rs | 87 ++++++++++++++++++---- crates/editor/src/multi_buffer/anchor.rs | 4 + crates/sum_tree/src/sum_tree.rs | 26 +------ 4 files changed, 79 insertions(+), 42 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 82cef10027832ee828aab598cb4a045f69051c90..240582df8cb1aea270ddd2ad499ba62cbd6db609 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -305,10 +305,10 @@ impl DisplayMap { let mut new_inlays = Vec::new(); for (&location, hints) in new_hints { for hint in hints { - let hint_anchor = + let mut hint_anchor = buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); new_inlays.push(InlayProperties { - position: hint_anchor, + position: hint_anchor.bias_left(&buffer_snapshot), text: hint.text(), }); } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 63edddbf8dc1c804d1d5883e0b45d2550ff82e23..9974c35bd293f00b60f905dda10b7f7257aa8784 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -5,7 +5,7 @@ use super::{ }, TextHighlights, }; -use crate::{Anchor, MultiBufferSnapshot, ToOffset, ToPoint}; +use crate::{Anchor, MultiBufferSnapshot, ToPoint}; use collections::{BTreeMap, HashMap, HashSet}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; @@ -150,8 +150,8 @@ pub struct Inlay { } #[derive(Debug, Clone)] -pub struct InlayProperties { - pub position: P, +pub struct InlayProperties { + pub position: Anchor, pub text: T, } @@ -307,6 +307,16 @@ impl InlayMap { ), ); + // Leave all the inlays starting at the end of the edit if they have a left bias. + while let Some(Transform::Inlay(inlay)) = cursor.item() { + if inlay.position.bias() == Bias::Left { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + cursor.next(&()); + } else { + break; + } + } + // Apply the edit. let new_start = InlayOffset(new_transforms.summary().output.len); let new_end = @@ -323,8 +333,9 @@ impl InlayMap { ), ); - // Push all the inlays starting at the end of the edit. + // Push all the inlays starting at the end of the edit if they have a right bias. while let Some(Transform::Inlay(inlay)) = cursor.item() { + debug_assert_eq!(inlay.position.bias(), Bias::Right); new_transforms.push(Transform::Inlay(inlay.clone()), &()); cursor.next(&()); } @@ -359,10 +370,10 @@ impl InlayMap { (new_snapshot, inlay_edits.into_inner()) } - pub fn splice>( + pub fn splice>( &mut self, to_remove: HashSet, - to_insert: Vec>, + to_insert: Vec>, ) -> (InlaySnapshot, Vec, Vec) { let mut snapshot = self.snapshot.lock(); @@ -371,7 +382,7 @@ impl InlayMap { for properties in to_insert { let inlay = Inlay { id: InlayId(post_inc(&mut self.next_inlay_id)), - position: snapshot.buffer_snapshot().anchor_after(properties.position), + position: properties.position, text: properties.text.into(), }; self.inlays.insert(inlay.id, inlay.clone()); @@ -384,7 +395,12 @@ impl InlayMap { .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - inlays.insert((suggestion_point, Reverse(inlay.id)), Some(inlay)); + // TODO kb consider changing Reverse to be dynamic depending on whether we appending to to the left or right of the anchor + // we want the newer (bigger) IDs to be closer to the "target" of the hint. + inlays.insert( + (suggestion_point, inlay.position.bias(), Reverse(inlay.id)), + Some(inlay), + ); } for inlay_id in to_remove { @@ -395,7 +411,10 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - inlays.insert((suggestion_point, Reverse(inlay.id)), None); + inlays.insert( + (suggestion_point, inlay.position.bias(), Reverse(inlay.id)), + None, + ); } } @@ -405,7 +424,7 @@ impl InlayMap { .transforms .cursor::<(SuggestionPoint, (InlayOffset, InlayPoint))>(); let mut inlays = inlays.into_iter().peekable(); - while let Some(((suggestion_point, inlay_id), inlay)) = inlays.next() { + while let Some(((suggestion_point, bias, inlay_id), inlay)) = inlays.next() { new_transforms.push_tree(cursor.slice(&suggestion_point, Bias::Left, &()), &()); while let Some(transform) = cursor.item() { @@ -419,7 +438,7 @@ impl InlayMap { } } Transform::Inlay(inlay) => { - if inlay.id > inlay_id.0 { + if (inlay.position.bias(), Reverse(inlay.id)) > (bias, inlay_id) { new_transforms.push(transform.clone(), &()); cursor.next(&()); } else { @@ -459,7 +478,7 @@ impl InlayMap { new_transforms.push(Transform::Inlay(inlay), &()); - if inlays.peek().map_or(true, |((suggestion_point, _), _)| { + if inlays.peek().map_or(true, |((suggestion_point, _, _), _)| { *suggestion_point >= cursor.end(&()).0 }) { let suffix_suggestion_end = cursor.end(&()).0; @@ -505,16 +524,21 @@ impl InlayMap { if self.inlays.is_empty() || rng.gen() { let buffer_snapshot = snapshot.buffer_snapshot(); let position = buffer_snapshot.random_byte_range(0, rng).start; + let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = rng.gen_range(1..=5); let text = util::RandomCharIter::new(&mut *rng) .take(len) .collect::(); log::info!( - "creating inlay at buffer offset {} with text {:?}", + "creating inlay at buffer offset {} with bias {:?} and text {:?}", position, + bias, text ); - to_insert.push(InlayProperties { position, text }); + to_insert.push(InlayProperties { + position: buffer_snapshot.anchor_at(position, bias), + text, + }); } else { to_remove.insert(*self.inlays.keys().choose(rng).unwrap()); } @@ -833,10 +857,10 @@ mod tests { let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abcdefghi"); - let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( + let (inlay_snapshot, _, _) = inlay_map.splice( HashSet::default(), vec![InlayProperties { - position: 3, + position: buffer.read(cx).snapshot(cx).anchor_after(3), text: "|123|", }], ); @@ -913,6 +937,37 @@ mod tests { suggestion_map.sync(fold_snapshot.clone(), fold_edits); let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); + + let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( + HashSet::default(), + vec![ + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|", + }, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|", + }, + ], + ); + assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); + + // Edits ending where the inlay starts should not move it if it has a left bias. + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx)); + let (fold_snapshot, fold_edits) = fold_map.read( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); + + // The inlays can be manually removed. + let (inlay_snapshot, _, _) = + inlay_map.splice::(HashSet::from_iter(inlay_ids), Default::default()); + assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } #[gpui::test(iterations = 100)] diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 9a5145c244880e37cd27992886d7a4740f5b902b..b308927cbb1dca319dd629b152c2fd7295afbdfe 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -49,6 +49,10 @@ impl Anchor { } } + pub fn bias(&self) -> Bias { + self.text_anchor.bias + } + pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Left { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index fa467886f03aa6109df1ef3670c4ce79a6f41466..577a942889694790343600ff8151a6609961839c 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -95,35 +95,13 @@ impl fmt::Debug for End { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash, Default)] pub enum Bias { + #[default] Left, Right, } -impl Default for Bias { - fn default() -> Self { - Bias::Left - } -} - -impl PartialOrd for Bias { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Bias { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (Self::Left, Self::Left) => Ordering::Equal, - (Self::Left, Self::Right) => Ordering::Less, - (Self::Right, Self::Right) => Ordering::Equal, - (Self::Right, Self::Left) => Ordering::Greater, - } - } -} - #[derive(Debug, Clone)] pub struct SumTree(Arc>); From addb62c1fc07fa2d80f6467ef85f659aa6be9165 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 12 Jun 2023 11:55:27 +0300 Subject: [PATCH 079/169] Fix the duplicate hints Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/display_map/inlay_map.rs | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 240582df8cb1aea270ddd2ad499ba62cbd6db609..59ba9d8f4e87ff511094938bfdcc20bfb5606c26 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -305,7 +305,7 @@ impl DisplayMap { let mut new_inlays = Vec::new(); for (&location, hints) in new_hints { for hint in hints { - let mut hint_anchor = + let hint_anchor = buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); new_inlays.push(InlayProperties { position: hint_anchor.bias_left(&buffer_snapshot), diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 9974c35bd293f00b60f905dda10b7f7257aa8784..9514e87b4a6ca60174e28b0fe57cc32f919a6477 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -11,7 +11,7 @@ use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; use std::{ - cmp::{self, Reverse}, + cmp, ops::{Add, AddAssign, Range, Sub}, }; use sum_tree::{Bias, Cursor, SumTree}; @@ -394,11 +394,8 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - - // TODO kb consider changing Reverse to be dynamic depending on whether we appending to to the left or right of the anchor - // we want the newer (bigger) IDs to be closer to the "target" of the hint. inlays.insert( - (suggestion_point, inlay.position.bias(), Reverse(inlay.id)), + (suggestion_point, inlay.position.bias(), inlay.id), Some(inlay), ); } @@ -411,10 +408,7 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - inlays.insert( - (suggestion_point, inlay.position.bias(), Reverse(inlay.id)), - None, - ); + inlays.insert((suggestion_point, inlay.position.bias(), inlay.id), None); } } @@ -424,7 +418,7 @@ impl InlayMap { .transforms .cursor::<(SuggestionPoint, (InlayOffset, InlayPoint))>(); let mut inlays = inlays.into_iter().peekable(); - while let Some(((suggestion_point, bias, inlay_id), inlay)) = inlays.next() { + while let Some(((suggestion_point, bias, inlay_id), inlay_to_insert)) = inlays.next() { new_transforms.push_tree(cursor.slice(&suggestion_point, Bias::Left, &()), &()); while let Some(transform) = cursor.item() { @@ -438,11 +432,11 @@ impl InlayMap { } } Transform::Inlay(inlay) => { - if (inlay.position.bias(), Reverse(inlay.id)) > (bias, inlay_id) { + if (inlay.position.bias(), inlay.id) < (bias, inlay_id) { new_transforms.push(transform.clone(), &()); cursor.next(&()); } else { - if inlay.id == inlay_id.0 { + if inlay.id == inlay_id { let new_start = InlayOffset(new_transforms.summary().output.len); inlay_edits.push(Edit { old: cursor.start().1 .0..cursor.end(&()).1 .0, @@ -456,7 +450,7 @@ impl InlayMap { } } - if let Some(inlay) = inlay { + if let Some(inlay) = inlay_to_insert { let prefix_suggestion_start = SuggestionPoint(new_transforms.summary().input.lines); push_isomorphic( &mut new_transforms, @@ -1052,7 +1046,7 @@ mod tests { (suggestion_offset, inlay.clone()) }) .collect::>(); - inlays.sort_by_key(|(offset, inlay)| (*offset, Reverse(inlay.id))); + inlays.sort_by_key(|(offset, inlay)| (*offset, inlay.position.bias(), inlay.id)); let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); for (offset, inlay) in inlays.into_iter().rev() { expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); From 271cd25a1d90b9b676febf83fb5dd2da81d1fd45 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 12 Jun 2023 17:16:48 +0300 Subject: [PATCH 080/169] Display excerpt-ranged hints only --- crates/editor/src/display_map.rs | 25 ++-- crates/editor/src/editor.rs | 168 ++++++++++-------------- crates/editor/src/inlay_hint_storage.rs | 43 ++++++ 3 files changed, 126 insertions(+), 110 deletions(-) create mode 100644 crates/editor/src/inlay_hint_storage.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 59ba9d8f4e87ff511094938bfdcc20bfb5606c26..40f6ecb76ee3af8131fa210c3cc4bc4f34017ec9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -6,8 +6,8 @@ mod tab_map; mod wrap_map; use crate::{ - display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, InlayHintLocation, - MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, MultiBuffer, + MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; @@ -287,7 +287,7 @@ impl DisplayMap { pub fn splice_inlays( &mut self, - new_hints: &HashMap>, + new_hints: Vec<(Anchor, project::InlayHint)>, cx: &mut ModelContext, ) { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); @@ -302,18 +302,13 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let mut new_inlays = Vec::new(); - for (&location, hints) in new_hints { - for hint in hints { - let hint_anchor = - buffer_snapshot.anchor_in_excerpt(location.excerpt_id, hint.position); - new_inlays.push(InlayProperties { - position: hint_anchor.bias_left(&buffer_snapshot), - text: hint.text(), - }); - } - } - + let new_inlays = new_hints + .into_iter() + .map(|(hint_anchor, hint)| InlayProperties { + position: hint_anchor.bias_left(&buffer_snapshot), + text: hint.text(), + }) + .collect(); let (snapshot, edits, _) = self.inlay_map.splice( // TODO kb this is wrong, calc diffs in the editor instead. self.inlay_map.inlays.keys().copied().collect(), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2d5224de32a3794860653739cd683c0ceee985de..ef1db6fcc6b1f38f8765715cb22f1cf61f1625eb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2,6 +2,7 @@ mod blink_manager; pub mod display_map; mod editor_settings; mod element; +mod inlay_hint_storage; mod git; mod highlight_matching_bracket; @@ -25,7 +26,7 @@ use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; -use clock::{Global, ReplicaId}; +use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; @@ -52,6 +53,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; +use inlay_hint_storage::InlayHintStorage; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -71,7 +73,9 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; +use project::{ + FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction, +}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -536,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_hint_versions: InlayHintVersions, + inlay_hint_storage: InlayHintStorage, _subscriptions: Vec, } @@ -1153,37 +1157,6 @@ impl CopilotState { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct InlayHintLocation { - pub buffer_id: u64, - pub excerpt_id: ExcerptId, -} - -// TODO kb -#[derive(Debug, Default, Clone)] -struct InlayHintVersions { - last_buffer_versions_with_hints: HashMap, -} - -impl InlayHintVersions { - fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool { - self.last_buffer_versions_with_hints - .get(location) - .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints)) - .unwrap_or(true) - } - - fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool { - if self.absent_or_newer(&location, &new_version) { - self.last_buffer_versions_with_hints - .insert(location, new_version); - true - } else { - false - } - } -} - #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, @@ -1324,7 +1297,7 @@ impl Editor { project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { match event { project::Event::ReloadInlayHints => { - editor.try_update_inlay_hints(cx); + editor.reload_inlay_hints(cx); } _ => {} }; @@ -1382,7 +1355,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_versions: InlayHintVersions::default(), + inlay_hint_storage: InlayHintStorage::default(), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2618,44 +2591,44 @@ impl Editor { } } - fn try_update_inlay_hints(&self, cx: &mut ViewContext) { + fn reload_inlay_hints(&self, cx: &mut ViewContext) { if self.mode != EditorMode::Full { return; } - let multi_buffer = self.buffer().read(cx); - let buffer_snapshot = multi_buffer.snapshot(cx); - let hint_fetch_tasks = buffer_snapshot - .excerpts() - .map(|(excerpt_id, excerpt_buffer_snapshot, _)| { - (excerpt_id, excerpt_buffer_snapshot.clone()) - }) - .map(|(excerpt_id, excerpt_buffer_snapshot)| { + let multi_buffer = self.buffer(); + let hint_fetch_tasks = multi_buffer + .read(cx) + .all_buffers() + .into_iter() + .map(|buffer_handle| { + let buffer = buffer_handle.read(cx); + // TODO kb every time I reopen the same buffer, it's different. + // Find a way to understand it's the same buffer. Use paths? + dbg!(buffer_handle.id()); + let buffer_id = dbg!(buffer.remote_id()); + let buffer_len = buffer.len(); + cx.spawn(|editor, mut cx| async move { let task = editor .update(&mut cx, |editor, cx| { - editor.project.as_ref().and_then(|project| { + editor.project.as_ref().map(|project| { project.update(cx, |project, cx| { - Some( - project.inlay_hints_for_buffer( - editor - .buffer() - .read(cx) - .buffer(excerpt_buffer_snapshot.remote_id())?, - 0..excerpt_buffer_snapshot.len(), - cx, - ), - ) + project.inlay_hints_for_buffer(buffer_handle, 0..buffer_len, cx) }) }) }) .context("inlay hints fecth task spawn")?; anyhow::Ok(( - excerpt_id, - excerpt_buffer_snapshot, + buffer_id, match task { - Some(task) => task.await.context("inlay hints for buffer task")?, + Some(task) => { + let mut buffer_hints = + task.await.context("inlay hints for buffer task")?; + buffer_hints.sort_unstable_by_key(|hint| hint.position.offset); + buffer_hints + } None => Vec::new(), }, )) @@ -2664,52 +2637,57 @@ impl Editor { .collect::>(); cx.spawn(|editor, mut cx| async move { - let mut new_hints: HashMap> = - HashMap::default(); + let mut hints_to_draw: Vec<(Anchor, InlayHint)> = Vec::new(); + let (multi_buffer, multi_buffer_snapshot) = editor.read_with(&cx, |editor, cx| { + let multi_buffer = editor.buffer().clone(); + let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); + (multi_buffer, multi_buffer_snapshot) + })?; + for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { - Ok((excerpt_id, excerpt_buffer_snapshot, excerpt_hints)) => { - let buffer_id = excerpt_buffer_snapshot.remote_id(); - let should_update_hints = editor - .update(&mut cx, |editor, _| { - // TODO kb wrong: need to query hints per buffer, not per excerpt - // need to store the previous state and calculate the diff between them, and calculate anchors here too. - editor.inlay_hint_versions.insert( - InlayHintLocation { - buffer_id, - excerpt_id, - }, - excerpt_buffer_snapshot.version().clone(), - ) - }) - .log_err() - .unwrap_or(false); - if should_update_hints { - new_hints - .entry(InlayHintLocation { - buffer_id, - excerpt_id, + Ok((buffer_id, sorted_buffer_hints)) => { + let Some(buffer_excerpts) = cx.read(|cx| { + let multi_buffer = multi_buffer.read(cx); + multi_buffer.buffer(buffer_id).map(|buffer| multi_buffer.excerpts_for_buffer(&buffer, cx)) + }) else { continue }; + for (excerpt_id, excerpt_range) in buffer_excerpts { + let excerpt_hints = sorted_buffer_hints + .iter() + .cloned() + .skip_while(|hint| { + hint.position.offset < excerpt_range.context.start.offset + }) + .take_while(|hint| { + hint.position.offset <= excerpt_range.context.end.offset }) - .or_default() - .extend(excerpt_hints); + .collect::>(); + + if !excerpt_hints.is_empty() { + hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| { + let anchor = multi_buffer_snapshot + .anchor_in_excerpt(excerpt_id, hint.position); + (anchor, hint) + })); + } } } Err(e) => error!("Failed to update hints for buffer: {e:#}"), } } - if !new_hints.is_empty() { - editor - .update(&mut cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(&new_hints, cx); - }); - }) - .log_err() - .unwrap_or(()) + // TODO kb calculate diffs using the storage instead + if !hints_to_draw.is_empty() { + editor.update(&mut cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(hints_to_draw, cx); + }); + })?; } + + anyhow::Ok(()) }) - .detach(); + .detach_and_log_err(cx); } fn trigger_on_type_formatting( @@ -7292,7 +7270,7 @@ impl Editor { }; if update_inlay_hints { - self.try_update_inlay_hints(cx); + self.reload_inlay_hints(cx); } } diff --git a/crates/editor/src/inlay_hint_storage.rs b/crates/editor/src/inlay_hint_storage.rs new file mode 100644 index 0000000000000000000000000000000000000000..90e5ea01fbbbdb766e39f03dd19c646241b1eaea --- /dev/null +++ b/crates/editor/src/inlay_hint_storage.rs @@ -0,0 +1,43 @@ +use crate::Anchor; +use project::InlayHint; + +use collections::BTreeMap; + +#[derive(Debug, Default)] +pub struct InlayHintStorage { + hints: BTreeMap, +} + +impl InlayHintStorage { + // TODO kb calculate the diff instead + fn insert(&mut self) -> bool { + todo!("TODO kb") + } +} + +// let buffer_version = +// cx.read(|cx| buffer.read(cx).version().clone()); + +// #[derive(Debug, Default, Clone)] +// struct InlayHintVersions { +// last_buffer_versions_with_hints: HashMap, +// } + +// impl InlayHintVersions { +// fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool { +// self.last_buffer_versions_with_hints +// .get(location) +// .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints)) +// .unwrap_or(true) +// } + +// fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool { +// if self.absent_or_newer(&location, &new_version) { +// self.last_buffer_versions_with_hints +// .insert(location, new_version); +// true +// } else { +// false +// } +// } +// } From 6d1068d1e94841f1a7b9deee31cef4b0562d8e4b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 12 Jun 2023 18:43:57 +0300 Subject: [PATCH 081/169] Query inlay hints for excerpt ranges only --- crates/editor/src/display_map/inlay_map.rs | 2 +- crates/editor/src/editor.rs | 74 ++++++++-------------- crates/editor/src/inlay_hint_storage.rs | 5 +- 3 files changed, 30 insertions(+), 51 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 9514e87b4a6ca60174e28b0fe57cc32f919a6477..e2308bfcc9fabced9ecf7b8822bd1d0c58efef85 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -29,7 +29,7 @@ pub struct InlayMap { #[derive(Clone)] pub struct InlaySnapshot { - // TODO kb merge these two together? + // TODO kb merge these two together pub suggestion_snapshot: SuggestionSnapshot, transforms: SumTree, pub version: usize, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ef1db6fcc6b1f38f8765715cb22f1cf61f1625eb..cd12dbefd163de9ebbe8988c44ed35e134e3d539 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2597,86 +2597,62 @@ impl Editor { } let multi_buffer = self.buffer(); - let hint_fetch_tasks = multi_buffer - .read(cx) - .all_buffers() - .into_iter() - .map(|buffer_handle| { - let buffer = buffer_handle.read(cx); + let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); + let hint_fetch_tasks = multi_buffer_snapshot + .excerpts() + .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { // TODO kb every time I reopen the same buffer, it's different. // Find a way to understand it's the same buffer. Use paths? - dbg!(buffer_handle.id()); - let buffer_id = dbg!(buffer.remote_id()); - let buffer_len = buffer.len(); + let buffer_id = buffer_snapshot.remote_id(); + let buffer_handle = multi_buffer.read(cx).buffer(buffer_id)?; - cx.spawn(|editor, mut cx| async move { + let task = cx.spawn(|editor, mut cx| async move { let task = editor .update(&mut cx, |editor, cx| { editor.project.as_ref().map(|project| { project.update(cx, |project, cx| { - project.inlay_hints_for_buffer(buffer_handle, 0..buffer_len, cx) + project.inlay_hints_for_buffer( + buffer_handle, + excerpt_range.context, + cx, + ) }) }) }) .context("inlay hints fecth task spawn")?; anyhow::Ok(( - buffer_id, + excerpt_id, match task { - Some(task) => { - let mut buffer_hints = - task.await.context("inlay hints for buffer task")?; - buffer_hints.sort_unstable_by_key(|hint| hint.position.offset); - buffer_hints - } + Some(task) => task.await.context("inlay hints for buffer task")?, None => Vec::new(), }, )) - }) + }); + Some(task) }) .collect::>(); cx.spawn(|editor, mut cx| async move { let mut hints_to_draw: Vec<(Anchor, InlayHint)> = Vec::new(); - let (multi_buffer, multi_buffer_snapshot) = editor.read_with(&cx, |editor, cx| { - let multi_buffer = editor.buffer().clone(); - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - (multi_buffer, multi_buffer_snapshot) - })?; + let multi_buffer_snapshot = + editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { - Ok((buffer_id, sorted_buffer_hints)) => { - let Some(buffer_excerpts) = cx.read(|cx| { - let multi_buffer = multi_buffer.read(cx); - multi_buffer.buffer(buffer_id).map(|buffer| multi_buffer.excerpts_for_buffer(&buffer, cx)) - }) else { continue }; - for (excerpt_id, excerpt_range) in buffer_excerpts { - let excerpt_hints = sorted_buffer_hints - .iter() - .cloned() - .skip_while(|hint| { - hint.position.offset < excerpt_range.context.start.offset - }) - .take_while(|hint| { - hint.position.offset <= excerpt_range.context.end.offset - }) - .collect::>(); - - if !excerpt_hints.is_empty() { - hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| { - let anchor = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, hint.position); - (anchor, hint) - })); - } + Ok((excerpt_id, excerpt_hints)) => { + if !excerpt_hints.is_empty() { + hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| { + let anchor = multi_buffer_snapshot + .anchor_in_excerpt(excerpt_id, hint.position); + (anchor, hint) + })); } } Err(e) => error!("Failed to update hints for buffer: {e:#}"), } } - // TODO kb calculate diffs using the storage instead if !hints_to_draw.is_empty() { editor.update(&mut cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { diff --git a/crates/editor/src/inlay_hint_storage.rs b/crates/editor/src/inlay_hint_storage.rs index 90e5ea01fbbbdb766e39f03dd19c646241b1eaea..fcb89fa9138863e97459de5a6ca03f63797408e7 100644 --- a/crates/editor/src/inlay_hint_storage.rs +++ b/crates/editor/src/inlay_hint_storage.rs @@ -9,11 +9,14 @@ pub struct InlayHintStorage { } impl InlayHintStorage { - // TODO kb calculate the diff instead fn insert(&mut self) -> bool { todo!("TODO kb") } } +// TODO kb need to understand different inlay hint update cases: +// * new hints from the new excerpt (no need to invalidate the cache) +// * new hints after /refresh or a text edit (whole cache should be purged) +// ??? revert/reopened files could get a speedup, if we don't truly delete the hints, but hide them in another var? // let buffer_version = // cx.read(|cx| buffer.read(cx).version().clone()); From 7abaf22b93ec892947e80f9e2bb179f8d6c046df Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 01:46:03 +0300 Subject: [PATCH 082/169] Generate proper inlay diffs for splice --- crates/editor/src/display_map.rs | 26 +-- crates/editor/src/display_map/inlay_map.rs | 96 +++++----- crates/editor/src/editor.rs | 64 +++++-- crates/editor/src/inlay_cache.rs | 204 +++++++++++++++++++++ crates/editor/src/inlay_hint_storage.rs | 46 ----- 5 files changed, 317 insertions(+), 119 deletions(-) create mode 100644 crates/editor/src/inlay_cache.rs delete mode 100644 crates/editor/src/inlay_hint_storage.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 40f6ecb76ee3af8131fa210c3cc4bc4f34017ec9..d8924f9692505997c6dd9b1d8d66dae07d80c3f0 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -6,8 +6,8 @@ mod tab_map; mod wrap_map; use crate::{ - display_map::inlay_map::InlayProperties, Anchor, AnchorRangeExt, MultiBuffer, - MultiBufferSnapshot, ToOffset, ToPoint, + display_map::inlay_map::InlayProperties, inlay_cache::InlayId, Anchor, AnchorRangeExt, + MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; @@ -287,7 +287,8 @@ impl DisplayMap { pub fn splice_inlays( &mut self, - new_hints: Vec<(Anchor, project::InlayHint)>, + to_remove: Vec, + to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, cx: &mut ModelContext, ) { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); @@ -302,18 +303,19 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let new_inlays = new_hints + let new_inlays = to_insert .into_iter() - .map(|(hint_anchor, hint)| InlayProperties { - position: hint_anchor.bias_left(&buffer_snapshot), - text: hint.text(), + .map(|(inlay_id, hint_anchor, hint)| { + ( + inlay_id, + InlayProperties { + position: hint_anchor.bias_left(&buffer_snapshot), + text: hint.text(), + }, + ) }) .collect(); - let (snapshot, edits, _) = self.inlay_map.splice( - // TODO kb this is wrong, calc diffs in the editor instead. - self.inlay_map.inlays.keys().copied().collect(), - new_inlays, - ); + let (snapshot, edits) = self.inlay_map.splice(to_remove, new_inlays); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e2308bfcc9fabced9ecf7b8822bd1d0c58efef85..216436bb11d8a73f924b6be79da1315c1574ca7a 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -5,8 +5,8 @@ use super::{ }, TextHighlights, }; -use crate::{Anchor, MultiBufferSnapshot, ToPoint}; -use collections::{BTreeMap, HashMap, HashSet}; +use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint}; +use collections::{BTreeMap, HashMap}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; @@ -16,14 +16,9 @@ use std::{ }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; -use util::post_inc; - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InlayId(usize); pub struct InlayMap { snapshot: Mutex, - next_inlay_id: usize, pub(super) inlays: HashMap, } @@ -247,7 +242,6 @@ impl InlayMap { ( Self { snapshot: Mutex::new(snapshot.clone()), - next_inlay_id: 0, inlays: HashMap::default(), }, snapshot, @@ -372,21 +366,19 @@ impl InlayMap { pub fn splice>( &mut self, - to_remove: HashSet, - to_insert: Vec>, - ) -> (InlaySnapshot, Vec, Vec) { + to_remove: Vec, + to_insert: Vec<(InlayId, InlayProperties)>, + ) -> (InlaySnapshot, Vec) { let mut snapshot = self.snapshot.lock(); let mut inlays = BTreeMap::new(); - let mut new_ids = Vec::new(); - for properties in to_insert { + for (id, properties) in to_insert { let inlay = Inlay { - id: InlayId(post_inc(&mut self.next_inlay_id)), + id, position: properties.position, text: properties.text.into(), }; self.inlays.insert(inlay.id, inlay.clone()); - new_ids.push(inlay.id); let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot @@ -501,20 +493,20 @@ impl InlayMap { snapshot.version += 1; snapshot.check_invariants(); - (snapshot.clone(), inlay_edits.into_inner(), new_ids) + (snapshot.clone(), inlay_edits.into_inner()) } #[cfg(any(test, feature = "test-support"))] pub fn randomly_mutate( &mut self, rng: &mut rand::rngs::StdRng, - ) -> (InlaySnapshot, Vec, Vec) { + ) -> (InlaySnapshot, Vec) { use rand::prelude::*; - let mut to_remove = HashSet::default(); - let mut to_insert = Vec::default(); + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); let snapshot = self.snapshot.lock(); - for _ in 0..rng.gen_range(1..=5) { + for i in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { let buffer_snapshot = snapshot.buffer_snapshot(); let position = buffer_snapshot.random_byte_range(0, rng).start; @@ -529,12 +521,15 @@ impl InlayMap { bias, text ); - to_insert.push(InlayProperties { - position: buffer_snapshot.anchor_at(position, bias), - text, - }); + to_insert.push(( + InlayId(i), + InlayProperties { + position: buffer_snapshot.anchor_at(position, bias), + text, + }, + )); } else { - to_remove.insert(*self.inlays.keys().choose(rng).unwrap()); + to_remove.push(*self.inlays.keys().choose(rng).unwrap()); } } @@ -841,6 +836,7 @@ mod tests { use settings::SettingsStore; use std::env; use text::Patch; + use util::post_inc; #[gpui::test] fn test_basic_inlays(cx: &mut AppContext) { @@ -850,13 +846,17 @@ mod tests { let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abcdefghi"); + let mut next_inlay_id = 0; - let (inlay_snapshot, _, _) = inlay_map.splice( - HashSet::default(), - vec![InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|123|", - }], + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![( + InlayId(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|123|", + }, + )], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( @@ -932,17 +932,23 @@ mod tests { let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); - let (inlay_snapshot, _, inlay_ids) = inlay_map.splice( - HashSet::default(), + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), vec![ - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(3), - text: "|123|", - }, - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|456|", - }, + ( + InlayId(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|", + }, + ), + ( + InlayId(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|", + }, + ), ], ); assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); @@ -959,8 +965,8 @@ mod tests { assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. - let (inlay_snapshot, _, _) = - inlay_map.splice::(HashSet::from_iter(inlay_ids), Default::default()); + let (inlay_snapshot, _) = + inlay_map.splice::(inlay_map.inlays.keys().copied().collect(), Vec::new()); assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } @@ -996,8 +1002,8 @@ mod tests { let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=29 => { - let (snapshot, edits, _) = inlay_map.randomly_mutate(&mut rng); - inlay_snapshot = snapshot; + let (snapshot, edits) = inlay_map.randomly_mutate(&mut rng); + log::info!("mutated text: {:?}", snapshot.text()); inlay_edits = Patch::new(edits); } 30..=59 => { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cd12dbefd163de9ebbe8988c44ed35e134e3d539..fac7d4033b9089588eba6561600cb82867000797 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2,7 +2,7 @@ mod blink_manager; pub mod display_map; mod editor_settings; mod element; -mod inlay_hint_storage; +mod inlay_cache; mod git; mod highlight_matching_bracket; @@ -26,8 +26,8 @@ use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; -use clock::ReplicaId; -use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; +use clock::{Global, ReplicaId}; +use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; @@ -53,7 +53,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_storage::InlayHintStorage; +use inlay_cache::{InlayCache, InlaysUpdate, OrderedByAnchorOffset}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -540,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_hint_storage: InlayHintStorage, + inlay_hint_cache: InlayCache, _subscriptions: Vec, } @@ -1355,7 +1355,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_storage: InlayHintStorage::default(), + inlay_hint_cache: InlayCache::default(), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2604,7 +2604,14 @@ impl Editor { // TODO kb every time I reopen the same buffer, it's different. // Find a way to understand it's the same buffer. Use paths? let buffer_id = buffer_snapshot.remote_id(); + let buffer_version = buffer_snapshot.version().clone(); let buffer_handle = multi_buffer.read(cx).buffer(buffer_id)?; + if self + .inlay_hint_cache + .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id) + { + return None; + } let task = cx.spawn(|editor, mut cx| async move { let task = editor @@ -2622,6 +2629,8 @@ impl Editor { .context("inlay hints fecth task spawn")?; anyhow::Ok(( + buffer_id, + buffer_version, excerpt_id, match task { Some(task) => task.await.context("inlay hints for buffer task")?, @@ -2634,29 +2643,52 @@ impl Editor { .collect::>(); cx.spawn(|editor, mut cx| async move { - let mut hints_to_draw: Vec<(Anchor, InlayHint)> = Vec::new(); + let mut hints_response: HashMap< + u64, + (Global, HashMap>), + > = HashMap::default(); let multi_buffer_snapshot = editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { - Ok((excerpt_id, excerpt_hints)) => { - if !excerpt_hints.is_empty() { - hints_to_draw.extend(excerpt_hints.into_iter().map(|hint| { - let anchor = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, hint.position); - (anchor, hint) - })); + Ok((buffer_id, buffer_version, excerpt_id, excerpt_hints)) => { + let excerpt_hints_response = HashMap::from_iter([( + excerpt_id, + excerpt_hints.into_iter().fold( + OrderedByAnchorOffset::default(), + |mut ordered_hints, hint| { + let anchor = multi_buffer_snapshot + .anchor_in_excerpt(excerpt_id, hint.position); + ordered_hints.add(anchor, hint); + ordered_hints + }, + ), + )]); + match hints_response.entry(buffer_id) { + hash_map::Entry::Occupied(mut o) => { + o.get_mut().1.extend(excerpt_hints_response); + } + hash_map::Entry::Vacant(v) => { + v.insert((buffer_version, excerpt_hints_response)); + } } } Err(e) => error!("Failed to update hints for buffer: {e:#}"), } } - if !hints_to_draw.is_empty() { + if !hints_response.is_empty() { + let InlaysUpdate { + to_remove, + to_insert, + } = editor.update(&mut cx, |editor, _| { + editor.inlay_hint_cache.update_inlays(hints_response) + })?; + editor.update(&mut cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(hints_to_draw, cx); + display_map.splice_inlays(to_remove, to_insert, cx); }); })?; } diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs new file mode 100644 index 0000000000000000000000000000000000000000..e5c48dcba2f7f7350956680bf4df54f0aa13f510 --- /dev/null +++ b/crates/editor/src/inlay_cache.rs @@ -0,0 +1,204 @@ +use std::cmp; + +use crate::{Anchor, ExcerptId}; +use clock::Global; +use project::InlayHint; +use util::post_inc; + +use collections::{BTreeMap, HashMap}; + +#[derive(Clone, Debug, Default)] +pub struct InlayCache { + inlays_per_buffer: HashMap, + next_inlay_id: usize, +} + +#[derive(Clone, Debug)] +pub struct OrderedByAnchorOffset(pub BTreeMap); + +impl OrderedByAnchorOffset { + pub fn add(&mut self, anchor: Anchor, t: T) { + self.0.insert(anchor.text_anchor.offset, (anchor, t)); + } + + fn into_ordered_elements(self) -> impl Iterator { + self.0.into_values() + } +} + +impl Default for OrderedByAnchorOffset { + fn default() -> Self { + Self(BTreeMap::default()) + } +} + +#[derive(Clone, Debug, Default)] +struct BufferInlays { + buffer_version: Global, + inlays_per_excerpts: HashMap>, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InlayId(pub usize); + +#[derive(Debug)] +pub struct InlaysUpdate { + pub to_remove: Vec, + pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, +} + +impl InlayCache { + pub fn inlays_up_to_date( + &self, + buffer_id: u64, + buffer_version: &Global, + excerpt_id: ExcerptId, + ) -> bool { + let Some(buffer_inlays) = self.inlays_per_buffer.get(&buffer_id) else { return false }; + let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version + || buffer_inlays.buffer_version.changed_since(buffer_version); + buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) + } + + pub fn update_inlays( + &mut self, + new_inlays: HashMap>)>, + ) -> InlaysUpdate { + let mut old_inlays = self.inlays_per_buffer.clone(); + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + + for (buffer_id, (buffer_version, new_buffer_inlays)) in new_inlays { + match old_inlays.remove(&buffer_id) { + Some(mut old_buffer_inlays) => { + for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { + if self.inlays_up_to_date(buffer_id, &buffer_version, excerpt_id) { + continue; + } + + let self_inlays_per_buffer = self + .inlays_per_buffer + .get_mut(&buffer_id) + .expect("element expected: `old_inlays.remove` returned `Some`"); + let mut new_excerpt_inlays = + new_excerpt_inlays.into_ordered_elements().fuse().peekable(); + if old_buffer_inlays + .inlays_per_excerpts + .remove(&excerpt_id) + .is_some() + { + let self_excerpt_inlays = self_inlays_per_buffer + .inlays_per_excerpts + .get_mut(&excerpt_id) + .expect("element expected: `old_excerpt_inlays` is `Some`"); + let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); + self_excerpt_inlays.0.retain( + |_, (old_anchor, (old_inlay_id, old_inlay))| { + let mut retain = false; + + while let Some(new_offset) = new_excerpt_inlays + .peek() + .map(|(new_anchor, _)| new_anchor.text_anchor.offset) + { + let old_offset = old_anchor.text_anchor.offset; + match new_offset.cmp(&old_offset) { + cmp::Ordering::Less => { + let (new_anchor, new_inlay) = + new_excerpt_inlays.next().expect( + "element expected: `peek` returned `Some`", + ); + hints_to_add.push(( + new_anchor, + ( + InlayId(post_inc(&mut self.next_inlay_id)), + new_inlay, + ), + )); + } + cmp::Ordering::Equal => { + let (new_anchor, new_inlay) = + new_excerpt_inlays.next().expect( + "element expected: `peek` returned `Some`", + ); + if &new_inlay == old_inlay { + retain = true; + } else { + hints_to_add.push(( + new_anchor, + ( + InlayId(post_inc( + &mut self.next_inlay_id, + )), + new_inlay, + ), + )); + } + } + cmp::Ordering::Greater => break, + } + } + + if !retain { + to_remove.push(*old_inlay_id); + } + retain + }, + ); + + for (new_anchor, (id, new_inlay)) in hints_to_add { + self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone())); + to_insert.push((id, new_anchor, new_inlay)); + } + } + + for (new_anchor, new_inlay) in new_excerpt_inlays { + let id = InlayId(post_inc(&mut self.next_inlay_id)); + self_inlays_per_buffer + .inlays_per_excerpts + .entry(excerpt_id) + .or_default() + .add(new_anchor, (id, new_inlay.clone())); + to_insert.push((id, new_anchor, new_inlay)); + } + } + } + None => { + let mut inlays_per_excerpts: HashMap< + ExcerptId, + OrderedByAnchorOffset<(InlayId, InlayHint)>, + > = HashMap::default(); + for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { + for (new_anchor, new_inlay) in new_ordered_inlays.into_ordered_elements() { + let id = InlayId(post_inc(&mut self.next_inlay_id)); + inlays_per_excerpts + .entry(new_excerpt_id) + .or_default() + .add(new_anchor, (id, new_inlay.clone())); + to_insert.push((id, new_anchor, new_inlay)); + } + } + self.inlays_per_buffer.insert( + buffer_id, + BufferInlays { + buffer_version, + inlays_per_excerpts, + }, + ); + } + } + } + + for (_, old_buffer_inlays) in old_inlays { + for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts { + for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() { + to_remove.push(id_to_remove); + } + } + } + + InlaysUpdate { + to_remove, + to_insert, + } + } +} diff --git a/crates/editor/src/inlay_hint_storage.rs b/crates/editor/src/inlay_hint_storage.rs deleted file mode 100644 index fcb89fa9138863e97459de5a6ca03f63797408e7..0000000000000000000000000000000000000000 --- a/crates/editor/src/inlay_hint_storage.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::Anchor; -use project::InlayHint; - -use collections::BTreeMap; - -#[derive(Debug, Default)] -pub struct InlayHintStorage { - hints: BTreeMap, -} - -impl InlayHintStorage { - fn insert(&mut self) -> bool { - todo!("TODO kb") - } -} -// TODO kb need to understand different inlay hint update cases: -// * new hints from the new excerpt (no need to invalidate the cache) -// * new hints after /refresh or a text edit (whole cache should be purged) -// ??? revert/reopened files could get a speedup, if we don't truly delete the hints, but hide them in another var? - -// let buffer_version = -// cx.read(|cx| buffer.read(cx).version().clone()); - -// #[derive(Debug, Default, Clone)] -// struct InlayHintVersions { -// last_buffer_versions_with_hints: HashMap, -// } - -// impl InlayHintVersions { -// fn absent_or_newer(&self, location: &InlayHintLocation, new_version: &Global) -> bool { -// self.last_buffer_versions_with_hints -// .get(location) -// .map(|last_version_with_hints| new_version.changed_since(&last_version_with_hints)) -// .unwrap_or(true) -// } - -// fn insert(&mut self, location: InlayHintLocation, new_version: Global) -> bool { -// if self.absent_or_newer(&location, &new_version) { -// self.last_buffer_versions_with_hints -// .insert(location, new_version); -// true -// } else { -// false -// } -// } -// } From e1f22c368496730e73c4aa2a2a1c3351db3e5ba2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 11:29:53 +0300 Subject: [PATCH 083/169] Cache anchors from all versions, remove out of range hints --- crates/editor/src/editor.rs | 123 +++++++++++++++++++------------ crates/editor/src/inlay_cache.rs | 52 +++++++++---- 2 files changed, 112 insertions(+), 63 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fac7d4033b9089588eba6561600cb82867000797..9bd9e3daebd1a226ff221fe4c08300f0996be4b8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2596,81 +2596,106 @@ impl Editor { return; } + struct HintRequestKey { + buffer_id: u64, + buffer_version: Global, + excerpt_id: ExcerptId, + } + let multi_buffer = self.buffer(); let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let hint_fetch_tasks = multi_buffer_snapshot .excerpts() - .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { + .map(|(excerpt_id, buffer_snapshot, excerpt_range)| { // TODO kb every time I reopen the same buffer, it's different. // Find a way to understand it's the same buffer. Use paths? let buffer_id = buffer_snapshot.remote_id(); let buffer_version = buffer_snapshot.version().clone(); - let buffer_handle = multi_buffer.read(cx).buffer(buffer_id)?; - if self - .inlay_hint_cache - .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id) - { - return None; - } + let buffer_handle = multi_buffer.read(cx).buffer(buffer_id); + let hints_up_to_date = + self.inlay_hint_cache + .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id); + let key = HintRequestKey { + buffer_id, + buffer_version, + excerpt_id, + }; - let task = cx.spawn(|editor, mut cx| async move { - let task = editor - .update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - project.inlay_hints_for_buffer( - buffer_handle, - excerpt_range.context, - cx, - ) + cx.spawn(|editor, mut cx| async move { + if hints_up_to_date { + anyhow::Ok((key, None)) + } else { + let Some(buffer_handle) = buffer_handle else { return Ok((key, Some(Vec::new()))) }; + let max_buffer_offset = cx.read(|cx| buffer_handle.read(cx).len()); + let excerpt_range = excerpt_range.context; + let query_start = excerpt_range.start.offset; + let query_end = excerpt_range.end.offset.min(max_buffer_offset); + let task = editor + .update(&mut cx, |editor, cx| { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + project.inlay_hints_for_buffer( + buffer_handle, + query_start..query_end, + cx, + ) + }) }) }) - }) - .context("inlay hints fecth task spawn")?; - - anyhow::Ok(( - buffer_id, - buffer_version, - excerpt_id, - match task { - Some(task) => task.await.context("inlay hints for buffer task")?, + .context("inlay hints fecth task spawn")?; + + Ok((key, Some(match task { + Some(task) => { + let mut new_hints = task.await.context("inlay hints for buffer task")?; + new_hints.retain(|hint| { + let hint_offset = hint.position.offset; + query_start <= hint_offset && hint_offset <= query_end + }); + new_hints + }, None => Vec::new(), - }, - )) - }); - Some(task) + }))) + } + }) }) .collect::>(); cx.spawn(|editor, mut cx| async move { - let mut hints_response: HashMap< + let mut inlay_updates: HashMap< u64, - (Global, HashMap>), + ( + Global, + HashMap>>, + ), > = HashMap::default(); let multi_buffer_snapshot = editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; for task_result in futures::future::join_all(hint_fetch_tasks).await { match task_result { - Ok((buffer_id, buffer_version, excerpt_id, excerpt_hints)) => { + Ok((request_key, response_inlays)) => { let excerpt_hints_response = HashMap::from_iter([( - excerpt_id, - excerpt_hints.into_iter().fold( - OrderedByAnchorOffset::default(), - |mut ordered_hints, hint| { - let anchor = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, hint.position); - ordered_hints.add(anchor, hint); - ordered_hints - }, - ), + request_key.excerpt_id, + response_inlays.map(|excerpt_hints| { + excerpt_hints.into_iter().fold( + OrderedByAnchorOffset::default(), + |mut ordered_hints, hint| { + let anchor = multi_buffer_snapshot.anchor_in_excerpt( + request_key.excerpt_id, + hint.position, + ); + ordered_hints.add(anchor, hint); + ordered_hints + }, + ) + }), )]); - match hints_response.entry(buffer_id) { + match inlay_updates.entry(request_key.buffer_id) { hash_map::Entry::Occupied(mut o) => { o.get_mut().1.extend(excerpt_hints_response); } hash_map::Entry::Vacant(v) => { - v.insert((buffer_version, excerpt_hints_response)); + v.insert((request_key.buffer_version, excerpt_hints_response)); } } } @@ -2678,12 +2703,12 @@ impl Editor { } } - if !hints_response.is_empty() { + if !inlay_updates.is_empty() { let InlaysUpdate { to_remove, to_insert, } = editor.update(&mut cx, |editor, _| { - editor.inlay_hint_cache.update_inlays(hints_response) + editor.inlay_hint_cache.update_inlays(inlay_updates) })?; editor.update(&mut cx, |editor, cx| { @@ -7244,7 +7269,7 @@ impl Editor { } multi_buffer::Event::Reparsed => { cx.emit(Event::Reparsed); - true + false } multi_buffer::Event::DirtyChanged => { cx.emit(Event::DirtyChanged); diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index e5c48dcba2f7f7350956680bf4df54f0aa13f510..d1b14f33428378b617151a7381512ac366d79987 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -1,7 +1,7 @@ use std::cmp; use crate::{Anchor, ExcerptId}; -use clock::Global; +use clock::{Global, Local}; use project::InlayHint; use util::post_inc; @@ -13,12 +13,22 @@ pub struct InlayCache { next_inlay_id: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AnchorKey { + offset: usize, + version: Local, +} + #[derive(Clone, Debug)] -pub struct OrderedByAnchorOffset(pub BTreeMap); +pub struct OrderedByAnchorOffset(pub BTreeMap); impl OrderedByAnchorOffset { pub fn add(&mut self, anchor: Anchor, t: T) { - self.0.insert(anchor.text_anchor.offset, (anchor, t)); + let key = AnchorKey { + offset: anchor.text_anchor.offset, + version: anchor.text_anchor.timestamp, + }; + self.0.insert(key, (anchor, t)); } fn into_ordered_elements(self) -> impl Iterator { @@ -62,13 +72,19 @@ impl InlayCache { pub fn update_inlays( &mut self, - new_inlays: HashMap>)>, + inlay_updates: HashMap< + u64, + ( + Global, + HashMap>>, + ), + >, ) -> InlaysUpdate { let mut old_inlays = self.inlays_per_buffer.clone(); let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - for (buffer_id, (buffer_version, new_buffer_inlays)) in new_inlays { + for (buffer_id, (buffer_version, new_buffer_inlays)) in inlay_updates { match old_inlays.remove(&buffer_id) { Some(mut old_buffer_inlays) => { for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { @@ -80,8 +96,12 @@ impl InlayCache { .inlays_per_buffer .get_mut(&buffer_id) .expect("element expected: `old_inlays.remove` returned `Some`"); - let mut new_excerpt_inlays = - new_excerpt_inlays.into_ordered_elements().fuse().peekable(); + let mut new_excerpt_inlays = match new_excerpt_inlays { + Some(new_inlays) => { + new_inlays.into_ordered_elements().fuse().peekable() + } + None => continue, + }; if old_buffer_inlays .inlays_per_excerpts .remove(&excerpt_id) @@ -168,13 +188,17 @@ impl InlayCache { OrderedByAnchorOffset<(InlayId, InlayHint)>, > = HashMap::default(); for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { - for (new_anchor, new_inlay) in new_ordered_inlays.into_ordered_elements() { - let id = InlayId(post_inc(&mut self.next_inlay_id)); - inlays_per_excerpts - .entry(new_excerpt_id) - .or_default() - .add(new_anchor, (id, new_inlay.clone())); - to_insert.push((id, new_anchor, new_inlay)); + if let Some(new_ordered_inlays) = new_ordered_inlays { + for (new_anchor, new_inlay) in + new_ordered_inlays.into_ordered_elements() + { + let id = InlayId(post_inc(&mut self.next_inlay_id)); + inlays_per_excerpts + .entry(new_excerpt_id) + .or_default() + .add(new_anchor, (id, new_inlay.clone())); + to_insert.push((id, new_anchor, new_inlay)); + } } } self.inlays_per_buffer.insert( From b3aa75a363c6b5db236677f52584e5c37ae3d06d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 11:47:54 +0300 Subject: [PATCH 084/169] Refresh inlays on buffer reopens --- crates/editor/src/editor.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9bd9e3daebd1a226ff221fe4c08300f0996be4b8..c1ec82b3493d8bf16b9afdc010a25d10b079e8c6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1299,6 +1299,9 @@ impl Editor { project::Event::ReloadInlayHints => { editor.reload_inlay_hints(cx); } + project::Event::ActiveEntryChanged(Some(_)) => { + editor.reload_inlay_hints(cx); + } _ => {} }; cx.notify() @@ -7299,7 +7302,7 @@ impl Editor { self.refresh_active_diagnostics(cx); false } - _ => true, + _ => false, }; if update_inlay_hints { From f155f5ded740ff144b6398ef635584c789353753 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 16:04:52 +0300 Subject: [PATCH 085/169] Better rpc inlay hint handling --- crates/editor/src/editor.rs | 81 ++++++++++++++-------------- crates/project/src/lsp_command.rs | 13 +++-- crates/project/src/project.rs | 88 ++++++++++++++++++++++++++++--- 3 files changed, 129 insertions(+), 53 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c1ec82b3493d8bf16b9afdc010a25d10b079e8c6..da2d0950a2a6b00a53e86dfac124b76676c5f4d5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -540,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_hint_cache: InlayCache, + inlay_cache: InlayCache, _subscriptions: Vec, } @@ -1295,16 +1295,10 @@ impl Editor { cx.emit(Event::TitleChanged); })); project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { - match event { - project::Event::ReloadInlayHints => { - editor.reload_inlay_hints(cx); - } - project::Event::ActiveEntryChanged(Some(_)) => { - editor.reload_inlay_hints(cx); - } - _ => {} + if let project::Event::RefreshInlays = event { + editor.refresh_inlays(cx); + cx.notify() }; - cx.notify() })); } } @@ -1358,7 +1352,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_cache: InlayCache::default(), + inlay_cache: InlayCache::default(), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2594,12 +2588,12 @@ impl Editor { } } - fn reload_inlay_hints(&self, cx: &mut ViewContext) { + fn refresh_inlays(&self, cx: &mut ViewContext) { if self.mode != EditorMode::Full { return; } - struct HintRequestKey { + struct InlayRequestKey { buffer_id: u64, buffer_version: Global, excerpt_id: ExcerptId, @@ -2607,7 +2601,7 @@ impl Editor { let multi_buffer = self.buffer(); let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let hint_fetch_tasks = multi_buffer_snapshot + let inlay_fetch_tasks = multi_buffer_snapshot .excerpts() .map(|(excerpt_id, buffer_snapshot, excerpt_range)| { // TODO kb every time I reopen the same buffer, it's different. @@ -2615,17 +2609,17 @@ impl Editor { let buffer_id = buffer_snapshot.remote_id(); let buffer_version = buffer_snapshot.version().clone(); let buffer_handle = multi_buffer.read(cx).buffer(buffer_id); - let hints_up_to_date = - self.inlay_hint_cache + let inlays_up_to_date = + self.inlay_cache .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id); - let key = HintRequestKey { + let key = InlayRequestKey { buffer_id, buffer_version, excerpt_id, }; cx.spawn(|editor, mut cx| async move { - if hints_up_to_date { + if inlays_up_to_date { anyhow::Ok((key, None)) } else { let Some(buffer_handle) = buffer_handle else { return Ok((key, Some(Vec::new()))) }; @@ -2645,19 +2639,24 @@ impl Editor { }) }) }) - .context("inlay hints fecth task spawn")?; + .context("inlays fecth task spawn")?; - Ok((key, Some(match task { + Ok((key, match task { Some(task) => { - let mut new_hints = task.await.context("inlay hints for buffer task")?; - new_hints.retain(|hint| { - let hint_offset = hint.position.offset; - query_start <= hint_offset && hint_offset <= query_end - }); - new_hints + match task.await.context("inlays for buffer task")? { + Some(mut new_inlays) => { + new_inlays.retain(|inlay| { + let inlay_offset = inlay.position.offset; + query_start <= inlay_offset && inlay_offset <= query_end + }); + Some(new_inlays) + }, + None => None, + } + }, - None => Vec::new(), - }))) + None => Some(Vec::new()), + })) } }) }) @@ -2674,35 +2673,35 @@ impl Editor { let multi_buffer_snapshot = editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; - for task_result in futures::future::join_all(hint_fetch_tasks).await { + for task_result in futures::future::join_all(inlay_fetch_tasks).await { match task_result { Ok((request_key, response_inlays)) => { - let excerpt_hints_response = HashMap::from_iter([( + let inlays_per_excerpt = HashMap::from_iter([( request_key.excerpt_id, - response_inlays.map(|excerpt_hints| { - excerpt_hints.into_iter().fold( + response_inlays.map(|excerpt_inlays| { + excerpt_inlays.into_iter().fold( OrderedByAnchorOffset::default(), - |mut ordered_hints, hint| { + |mut ordered_inlays, inlay| { let anchor = multi_buffer_snapshot.anchor_in_excerpt( request_key.excerpt_id, - hint.position, + inlay.position, ); - ordered_hints.add(anchor, hint); - ordered_hints + ordered_inlays.add(anchor, inlay); + ordered_inlays }, ) }), )]); match inlay_updates.entry(request_key.buffer_id) { hash_map::Entry::Occupied(mut o) => { - o.get_mut().1.extend(excerpt_hints_response); + o.get_mut().1.extend(inlays_per_excerpt); } hash_map::Entry::Vacant(v) => { - v.insert((request_key.buffer_version, excerpt_hints_response)); + v.insert((request_key.buffer_version, inlays_per_excerpt)); } } } - Err(e) => error!("Failed to update hints for buffer: {e:#}"), + Err(e) => error!("Failed to update inlays for buffer: {e:#}"), } } @@ -2711,7 +2710,7 @@ impl Editor { to_remove, to_insert, } = editor.update(&mut cx, |editor, _| { - editor.inlay_hint_cache.update_inlays(inlay_updates) + editor.inlay_cache.update_inlays(inlay_updates) })?; editor.update(&mut cx, |editor, cx| { @@ -7306,7 +7305,7 @@ impl Editor { }; if update_inlay_hints { - self.reload_inlay_hints(cx); + self.refresh_inlays(cx); } } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 85fec37eb882a383d2f9ce8ab53d80e4d390a724..5df37223c02d7e8d9b282745cb6c11af3a1019af 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1834,7 +1834,7 @@ impl LspCommand for InlayHints { .unwrap_or_default() .into_iter() .map(|lsp_hint| InlayHint { - buffer_id: buffer.id(), + buffer_id: origin_buffer.remote_id(), position: origin_buffer.anchor_after( origin_buffer .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), @@ -1931,7 +1931,7 @@ impl LspCommand for InlayHints { _: &mut Project, _: PeerId, buffer_version: &clock::Global, - _: &mut AppContext, + cx: &mut AppContext, ) -> proto::InlayHintsResponse { proto::InlayHintsResponse { hints: response @@ -1958,7 +1958,7 @@ impl LspCommand for InlayHints { location: label_part.location.map(|location| proto::Location { start: Some(serialize_anchor(&location.range.start)), end: Some(serialize_anchor(&location.range.end)), - buffer_id: location.buffer.id() as u64, + buffer_id: location.buffer.read(cx).remote_id(), }), }).collect() }) @@ -2005,8 +2005,13 @@ impl LspCommand for InlayHints { let mut hints = Vec::new(); for message_hint in message.hints { + let buffer_id = message_hint + .position + .as_ref() + .and_then(|location| location.buffer_id) + .context("missing buffer id")?; let hint = InlayHint { - buffer_id: buffer.id(), + buffer_id, position: message_hint .position .and_then(language::proto::deserialize_anchor) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ce42f6df114766534e55f74300f44bdbac2e143a..bda93a0206d1ead0dcf5440c3ef1407b226ae42e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -278,7 +278,7 @@ pub enum Event { new_peer_id: proto::PeerId, }, CollaboratorLeft(proto::PeerId), - ReloadInlayHints, + RefreshInlays, } pub enum LanguageServerState { @@ -329,7 +329,7 @@ pub struct Location { #[derive(Debug, Clone, PartialEq, Eq)] pub struct InlayHint { - pub buffer_id: usize, + pub buffer_id: u64, pub position: Anchor, pub label: InlayHintLabel, pub kind: Option, @@ -2830,7 +2830,7 @@ impl Project { .upgrade(&cx) .ok_or_else(|| anyhow!("project dropped"))?; this.update(&mut cx, |_, cx| { - cx.emit(Event::ReloadInlayHints); + cx.emit(Event::RefreshInlays); }); Ok(()) } @@ -4908,10 +4908,65 @@ impl Project { buffer_handle: ModelHandle, range: Range, cx: &mut ModelContext, - ) -> Task>> { + ) -> Task>>> { let buffer = buffer_handle.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); - self.request_lsp(buffer_handle, InlayHints { range }, cx) + let range_start = range.start; + let range_end = range.end; + let buffer_id = buffer.remote_id(); + let buffer_version = buffer.version().clone(); + let lsp_request = InlayHints { range }; + + if self.is_local() { + let lsp_request_task = self.request_lsp(buffer_handle.clone(), lsp_request, cx); + cx.spawn(|_, mut cx| async move { + buffer_handle + .update(&mut cx, |buffer, _| { + buffer.wait_for_edits(vec![range_start.timestamp, range_end.timestamp]) + }) + .await + .context("waiting for inlay hint request range edits")?; + + match lsp_request_task.await { + Ok(hints) => Ok(Some(hints)), + Err(e) if is_content_modified_error(&e) => Ok(None), + Err(other_e) => Err(other_e).context("inlay hints LSP request"), + } + }) + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + let request = proto::InlayHints { + project_id, + buffer_id, + start: Some(serialize_anchor(&range_start)), + end: Some(serialize_anchor(&range_end)), + version: serialize_version(&buffer_version), + }; + cx.spawn(|project, cx| async move { + let response = client + .request(request) + .await + .context("inlay hints proto request")?; + let hints_request_result = LspCommand::response_from_proto( + lsp_request, + response, + project, + buffer_handle, + cx, + ) + .await; + + match hints_request_result { + Ok(hints) => Ok(Some(hints)), + Err(e) if is_content_modified_error(&e) => Ok(None), + Err(other_err) => { + Err(other_err).context("inlay hints proto response conversion") + } + } + }) + } else { + Task::ready(Err(anyhow!("project does not have a remote id"))) + } } #[allow(clippy::type_complexity)] @@ -6686,11 +6741,23 @@ impl Project { let buffer_hints = this .update(&mut cx, |project, cx| { - let end = buffer.read(cx).len(); - project.inlay_hints_for_buffer(buffer, 0..end, cx) + let buffer_end = buffer.read(cx).len(); + project.inlay_hints_for_buffer( + buffer, + envelope + .payload + .start + .map_or(0, |anchor| anchor.offset as usize) + ..envelope + .payload + .end + .map_or(buffer_end, |anchor| anchor.offset as usize), + cx, + ) }) .await - .context("inlay hints fetch")?; + .context("inlay hints fetch")? + .unwrap_or_default(); Ok(this.update(&mut cx, |project, cx| { InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx) @@ -7765,3 +7832,8 @@ async fn wait_for_loading_buffer( receiver.next().await; } } + +// TODO kb what are better ways? +fn is_content_modified_error(error: &anyhow::Error) -> bool { + format!("{error:#}").contains("content modified") +} From 8acc5cf8f47885502366c2d2161c8f6db997bff7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 16:34:38 +0300 Subject: [PATCH 086/169] Deserialize more LSP inlay hint information --- crates/editor/src/display_map.rs | 11 ++++++++++- crates/project/src/lsp_command.rs | 21 +++++++++++++++++---- crates/project/src/project.rs | 27 ++++++++++++++++++++++++++- crates/rpc/proto/zed.proto | 4 +++- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d8924f9692505997c6dd9b1d8d66dae07d80c3f0..11f43bea65aabf73b8d6c5239c0516a32cd937a5 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -306,11 +306,20 @@ impl DisplayMap { let new_inlays = to_insert .into_iter() .map(|(inlay_id, hint_anchor, hint)| { + let mut text = hint.text(); + // TODO kb styling instead? + if hint.padding_right { + text.push(' '); + } + if hint.padding_left { + text.insert(0, ' '); + } + ( inlay_id, InlayProperties { position: hint_anchor.bias_left(&buffer_snapshot), - text: hint.text(), + text, }, ) }) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 5df37223c02d7e8d9b282745cb6c11af3a1019af..bce9bf0e10f09ede25de1019de53504df7045471 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,5 +1,5 @@ use crate::{ - DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, + DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, MarkupContent, Project, ProjectTransaction, }; @@ -1839,6 +1839,8 @@ impl LspCommand for InlayHints { origin_buffer .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), ), + padding_left: lsp_hint.padding_left.unwrap_or(false), + padding_right: lsp_hint.padding_right.unwrap_or(false), label: match lsp_hint.label { lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), lsp::InlayHintLabel::LabelParts(lsp_parts) => InlayHintLabel::LabelParts( @@ -1878,7 +1880,11 @@ impl LspCommand for InlayHints { .collect(), ), }, - kind: lsp_hint.kind.map(|kind| format!("{kind:?}")), + kind: lsp_hint.kind.and_then(|kind| match kind { + lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), + lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), + _ => None, + }), tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), lsp::InlayHintTooltip::MarkupContent(markup_content) => { @@ -1938,6 +1944,8 @@ impl LspCommand for InlayHints { .into_iter() .map(|response_hint| proto::InlayHint { position: Some(language::proto::serialize_anchor(&response_hint.position)), + padding_left: response_hint.padding_left, + padding_right: response_hint.padding_right, label: Some(proto::InlayHintLabel { label: Some(match response_hint.label { InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s), @@ -1965,7 +1973,7 @@ impl LspCommand for InlayHints { } }), }), - kind: response_hint.kind, + kind: response_hint.kind.map(|kind| kind.name().to_string()), tooltip: response_hint.tooltip.map(|response_tooltip| { let proto_tooltip = match response_tooltip { InlayHintTooltip::String(s) => { @@ -2061,7 +2069,12 @@ impl LspCommand for InlayHints { InlayHintLabel::LabelParts(label_parts) } }, - kind: message_hint.kind, + padding_left: message_hint.padding_left, + padding_right: message_hint.padding_right, + kind: message_hint + .kind + .as_deref() + .and_then(InlayHintKind::from_name), tooltip: message_hint.tooltip.and_then(|tooltip| { Some(match tooltip.content? { proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bda93a0206d1ead0dcf5440c3ef1407b226ae42e..2f22201ea42a80c1c6e27b57990e31798693a0ef 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -332,10 +332,35 @@ pub struct InlayHint { pub buffer_id: u64, pub position: Anchor, pub label: InlayHintLabel, - pub kind: Option, + pub kind: Option, + pub padding_left: bool, + pub padding_right: bool, pub tooltip: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InlayHintKind { + Type, + Parameter, +} + +impl InlayHintKind { + pub fn from_name(name: &str) -> Option { + match name { + "type" => Some(InlayHintKind::Type), + "parameter" => Some(InlayHintKind::Parameter), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + InlayHintKind::Type => "type", + InlayHintKind::Parameter => "parameter", + } + } +} + impl InlayHint { pub fn text(&self) -> String { match &self.label { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 6de98c4595847ddc00b1f8c5cad42f5e924102af..838a0123c071089ad499408e433688c25a521f63 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -725,7 +725,9 @@ message InlayHint { Anchor position = 1; InlayHintLabel label = 2; optional string kind = 3; - InlayHintTooltip tooltip = 4; + bool padding_left = 4; + bool padding_right = 5; + InlayHintTooltip tooltip = 6; } message InlayHintLabel { From ea837a183bc9f063060df64178c96005a26b421e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 17:14:38 +0300 Subject: [PATCH 087/169] Store inlays per paths and query on editor open --- crates/editor/src/editor.rs | 31 +++++++++++++++++-------------- crates/editor/src/inlay_cache.rs | 24 ++++++++++++++---------- crates/project/src/project.rs | 6 +++--- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index da2d0950a2a6b00a53e86dfac124b76676c5f4d5..93535c94804d38d5121bafd03b6b2dba6e982cbb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -84,6 +84,7 @@ use serde::{Deserialize, Serialize}; use settings::SettingsStore; use smallvec::SmallVec; use snippet::Snippet; +use std::path::PathBuf; use std::{ any::TypeId, borrow::Cow, @@ -1297,7 +1298,6 @@ impl Editor { project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { editor.refresh_inlays(cx); - cx.notify() }; })); } @@ -1352,6 +1352,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), + // TODO kb has to live between editors inlay_cache: InlayCache::default(), gutter_hovered: false, _subscriptions: vec![ @@ -1377,6 +1378,7 @@ impl Editor { } this.report_editor_event("open", None, cx); + this.refresh_inlays(cx); this } @@ -2594,7 +2596,7 @@ impl Editor { } struct InlayRequestKey { - buffer_id: u64, + buffer_path: PathBuf, buffer_version: Global, excerpt_id: ExcerptId, } @@ -2603,22 +2605,21 @@ impl Editor { let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let inlay_fetch_tasks = multi_buffer_snapshot .excerpts() - .map(|(excerpt_id, buffer_snapshot, excerpt_range)| { - // TODO kb every time I reopen the same buffer, it's different. - // Find a way to understand it's the same buffer. Use paths? + .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { + let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?; let buffer_id = buffer_snapshot.remote_id(); let buffer_version = buffer_snapshot.version().clone(); let buffer_handle = multi_buffer.read(cx).buffer(buffer_id); let inlays_up_to_date = self.inlay_cache - .inlays_up_to_date(buffer_id, &buffer_version, excerpt_id); + .inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id); let key = InlayRequestKey { - buffer_id, + buffer_path, buffer_version, excerpt_id, }; - cx.spawn(|editor, mut cx| async move { + let task = cx.spawn(|editor, mut cx| async move { if inlays_up_to_date { anyhow::Ok((key, None)) } else { @@ -2631,7 +2632,7 @@ impl Editor { .update(&mut cx, |editor, cx| { editor.project.as_ref().map(|project| { project.update(cx, |project, cx| { - project.inlay_hints_for_buffer( + project.query_inlay_hints_for_buffer( buffer_handle, query_start..query_end, cx, @@ -2658,13 +2659,15 @@ impl Editor { None => Some(Vec::new()), })) } - }) + }); + + Some(task) }) .collect::>(); cx.spawn(|editor, mut cx| async move { let mut inlay_updates: HashMap< - u64, + PathBuf, ( Global, HashMap>>, @@ -2692,7 +2695,7 @@ impl Editor { ) }), )]); - match inlay_updates.entry(request_key.buffer_id) { + match inlay_updates.entry(request_key.buffer_path) { hash_map::Entry::Occupied(mut o) => { o.get_mut().1.extend(inlays_per_excerpt); } @@ -7243,7 +7246,7 @@ impl Editor { event: &multi_buffer::Event, cx: &mut ViewContext, ) { - let update_inlay_hints = match event { + let refresh_inlay_hints = match event { multi_buffer::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); @@ -7304,7 +7307,7 @@ impl Editor { _ => false, }; - if update_inlay_hints { + if refresh_inlay_hints { self.refresh_inlays(cx); } } diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index d1b14f33428378b617151a7381512ac366d79987..2f5f4204b9d3cd1148b7135187fc5c4c23e3b465 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -1,4 +1,7 @@ -use std::cmp; +use std::{ + cmp, + path::{Path, PathBuf}, +}; use crate::{Anchor, ExcerptId}; use clock::{Global, Local}; @@ -9,7 +12,7 @@ use collections::{BTreeMap, HashMap}; #[derive(Clone, Debug, Default)] pub struct InlayCache { - inlays_per_buffer: HashMap, + inlays_per_buffer: HashMap, next_inlay_id: usize, } @@ -60,11 +63,11 @@ pub struct InlaysUpdate { impl InlayCache { pub fn inlays_up_to_date( &self, - buffer_id: u64, + buffer_path: &Path, buffer_version: &Global, excerpt_id: ExcerptId, ) -> bool { - let Some(buffer_inlays) = self.inlays_per_buffer.get(&buffer_id) else { return false }; + let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version || buffer_inlays.buffer_version.changed_since(buffer_version); buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) @@ -73,7 +76,7 @@ impl InlayCache { pub fn update_inlays( &mut self, inlay_updates: HashMap< - u64, + PathBuf, ( Global, HashMap>>, @@ -84,17 +87,17 @@ impl InlayCache { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - for (buffer_id, (buffer_version, new_buffer_inlays)) in inlay_updates { - match old_inlays.remove(&buffer_id) { + for (buffer_path, (buffer_version, new_buffer_inlays)) in inlay_updates { + match old_inlays.remove(&buffer_path) { Some(mut old_buffer_inlays) => { for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { - if self.inlays_up_to_date(buffer_id, &buffer_version, excerpt_id) { + if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { continue; } let self_inlays_per_buffer = self .inlays_per_buffer - .get_mut(&buffer_id) + .get_mut(&buffer_path) .expect("element expected: `old_inlays.remove` returned `Some`"); let mut new_excerpt_inlays = match new_excerpt_inlays { Some(new_inlays) => { @@ -112,6 +115,7 @@ impl InlayCache { .get_mut(&excerpt_id) .expect("element expected: `old_excerpt_inlays` is `Some`"); let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); + // TODO kb update inner buffer_id and version with the new data? self_excerpt_inlays.0.retain( |_, (old_anchor, (old_inlay_id, old_inlay))| { let mut retain = false; @@ -202,7 +206,7 @@ impl InlayCache { } } self.inlays_per_buffer.insert( - buffer_id, + buffer_path, BufferInlays { buffer_version, inlays_per_excerpts, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2f22201ea42a80c1c6e27b57990e31798693a0ef..5c08ff6b8249c813050762e87d7dfdc052028cf2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4928,7 +4928,7 @@ impl Project { ) } - pub fn inlay_hints_for_buffer( + pub fn query_inlay_hints_for_buffer( &self, buffer_handle: ModelHandle, range: Range, @@ -4951,7 +4951,6 @@ impl Project { }) .await .context("waiting for inlay hint request range edits")?; - match lsp_request_task.await { Ok(hints) => Ok(Some(hints)), Err(e) if is_content_modified_error(&e) => Ok(None), @@ -6767,7 +6766,8 @@ impl Project { let buffer_hints = this .update(&mut cx, |project, cx| { let buffer_end = buffer.read(cx).len(); - project.inlay_hints_for_buffer( + // TODO kb use cache before querying? + project.query_inlay_hints_for_buffer( buffer, envelope .payload From 1ed52276e0007a22f51dff2b3dbb93898ce8abb7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 13 Jun 2023 17:31:57 +0300 Subject: [PATCH 088/169] Add inlay hint settings --- assets/settings/default.json | 10 +++++++++ crates/editor/src/editor.rs | 33 ++++++++++++++++++++++++---- crates/editor/src/editor_settings.rs | 18 +++++++++++++++ crates/editor/src/inlay_cache.rs | 18 +++++++++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index c69d8089bc6e91e616da0c12bd90da277c78fefe..c413db5788a0f15e417a4c299f3be123a24dfa5c 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -73,6 +73,16 @@ // Whether to show git diff indicators in the scrollbar. "git_diff": true }, + // Inlay hint related settings + "inlay_hints": { + // Global switch to toggle hints on and off, switched off by default. + "enabled": false, + // Toggle certain types of hints on and off, all switched on by default. + "show_type_hints": true, + "show_parameter_hints": true, + // Corresponds to null/None LSP hint type value. + "show_other_hints": true + }, "project_panel": { // Whether to show the git status in the project panel. "git_status": true, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 93535c94804d38d5121bafd03b6b2dba6e982cbb..afe82bddc54eec47923d4e0c4ae557ee18a3ebca 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -74,7 +74,8 @@ pub use multi_buffer::{ use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; use project::{ - FormatTrigger, InlayHint, Location, LocationLink, Project, ProjectPath, ProjectTransaction, + FormatTrigger, InlayHint, InlayHintKind, Location, LocationLink, Project, ProjectPath, + ProjectTransaction, }; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, @@ -2590,11 +2591,20 @@ impl Editor { } } - fn refresh_inlays(&self, cx: &mut ViewContext) { + fn refresh_inlays(&mut self, cx: &mut ViewContext) { if self.mode != EditorMode::Full { return; } + let inlay_hint_settings = settings::get::(cx).inlay_hints; + if !inlay_hint_settings.enabled { + let to_remove = self.inlay_cache.clear(); + self.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, Vec::new(), cx); + }); + return; + } + struct InlayRequestKey { buffer_path: PathBuf, buffer_version: Global, @@ -2619,6 +2629,9 @@ impl Editor { excerpt_id, }; + // TODO kb split this into 2 different steps: + // 1. cache population + // 2. cache querying + hint filters on top (needs to store previous filter settings) let task = cx.spawn(|editor, mut cx| async move { if inlays_up_to_date { anyhow::Ok((key, None)) @@ -2646,9 +2659,20 @@ impl Editor { Some(task) => { match task.await.context("inlays for buffer task")? { Some(mut new_inlays) => { + let mut allowed_inlay_hint_types = Vec::new(); + if inlay_hint_settings.show_type_hints { + allowed_inlay_hint_types.push(Some(InlayHintKind::Type)); + } + if inlay_hint_settings.show_parameter_hints { + allowed_inlay_hint_types.push(Some(InlayHintKind::Parameter)); + } + if inlay_hint_settings.show_other_hints { + allowed_inlay_hint_types.push(None); + } new_inlays.retain(|inlay| { let inlay_offset = inlay.position.offset; - query_start <= inlay_offset && inlay_offset <= query_end + allowed_inlay_hint_types.contains(&inlay.kind) + && query_start <= inlay_offset && inlay_offset <= query_end }); Some(new_inlays) }, @@ -2713,7 +2737,7 @@ impl Editor { to_remove, to_insert, } = editor.update(&mut cx, |editor, _| { - editor.inlay_cache.update_inlays(inlay_updates) + dbg!(editor.inlay_cache.update_inlays(inlay_updates)) })?; editor.update(&mut cx, |editor, cx| { @@ -7318,6 +7342,7 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); + self.refresh_inlays(cx); } pub fn set_searchable(&mut self, searchable: bool) { diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 387d4d2c340d2a3bb5b648d8232880de2d8f7fe1..557c3194c0436c4ede60d623c43fb677a176363c 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -9,6 +9,7 @@ pub struct EditorSettings { pub show_completions_on_input: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, + pub inlay_hints: InlayHints, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -17,6 +18,14 @@ pub struct Scrollbar { pub git_diff: bool, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHints { + pub enabled: bool, + pub show_type_hints: bool, + pub show_parameter_hints: bool, + pub show_other_hints: bool, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { @@ -33,6 +42,7 @@ pub struct EditorSettingsContent { pub show_completions_on_input: Option, pub use_on_type_format: Option, pub scrollbar: Option, + pub inlay_hints: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -41,6 +51,14 @@ pub struct ScrollbarContent { pub git_diff: Option, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHintsContent { + pub enabled: Option, + pub show_type_hints: Option, + pub show_parameter_hints: Option, + pub show_other_hints: Option, +} + impl Setting for EditorSettings { const KEY: Option<&'static str> = None; diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index 2f5f4204b9d3cd1148b7135187fc5c4c23e3b465..c563102544c4eba5ce8a96d1cf73f65a5f5f99c6 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -229,4 +229,22 @@ impl InlayCache { to_insert, } } + + pub fn clear(&mut self) -> Vec { + self.inlays_per_buffer + .drain() + .map(|(_, buffer_inlays)| { + buffer_inlays + .inlays_per_excerpts + .into_iter() + .map(|(_, excerpt_inlays)| { + excerpt_inlays + .into_ordered_elements() + .map(|(_, (id, _))| id) + }) + .flatten() + }) + .flatten() + .collect() + } } From c898298c5cfe96fdc77e69d3e06dc6c63d7c3b09 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Jun 2023 00:39:51 +0300 Subject: [PATCH 089/169] Properly update inlay hints when settings are changed --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/editor.rs | 236 ++++++++++------------------- crates/editor/src/inlay_cache.rs | 248 ++++++++++++++++++++++++++++--- crates/project/src/project.rs | 18 +-- 4 files changed, 315 insertions(+), 189 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 11f43bea65aabf73b8d6c5239c0516a32cd937a5..5d2207d1f9cbd8afdba5f433a26588b5c94eef58 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -303,7 +303,7 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let new_inlays = to_insert + let new_inlays: Vec<(InlayId, InlayProperties)> = to_insert .into_iter() .map(|(inlay_id, hint_anchor, hint)| { let mut text = hint.text(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index afe82bddc54eec47923d4e0c4ae557ee18a3ebca..22a2c985aeb49d462fc32d5a66025a57fb960e5b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,4 +1,5 @@ mod blink_manager; + pub mod display_map; mod editor_settings; mod element; @@ -26,8 +27,8 @@ use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; -use clock::{Global, ReplicaId}; -use collections::{hash_map, BTreeMap, Bound, HashMap, HashSet, VecDeque}; +use clock::ReplicaId; +use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; @@ -53,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_cache::{InlayCache, InlaysUpdate, OrderedByAnchorOffset}; +use inlay_cache::{InlayCache, InlayRefreshReason, InlaysUpdate, QueryInlaysRange}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -73,10 +74,7 @@ pub use multi_buffer::{ }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; -use project::{ - FormatTrigger, InlayHint, InlayHintKind, Location, LocationLink, Project, ProjectPath, - ProjectTransaction, -}; +use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; @@ -85,7 +83,6 @@ use serde::{Deserialize, Serialize}; use settings::SettingsStore; use smallvec::SmallVec; use snippet::Snippet; -use std::path::PathBuf; use std::{ any::TypeId, borrow::Cow, @@ -1291,14 +1288,16 @@ impl Editor { (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); let mut project_subscriptions = Vec::new(); - if mode == EditorMode::Full && buffer.read(cx).is_singleton() { + if mode == EditorMode::Full { if let Some(project) = project.as_ref() { - project_subscriptions.push(cx.observe(project, |_, _, cx| { - cx.emit(Event::TitleChanged); - })); + if buffer.read(cx).is_singleton() { + project_subscriptions.push(cx.observe(project, |_, _, cx| { + cx.emit(Event::TitleChanged); + })); + } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(cx); + editor.refresh_inlays(InlayRefreshReason::Regular, cx); }; })); } @@ -1353,8 +1352,8 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - // TODO kb has to live between editors - inlay_cache: InlayCache::default(), + // TODO kb has to live between editor reopens + inlay_cache: InlayCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -1379,7 +1378,7 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(cx); + this.refresh_inlays(InlayRefreshReason::Regular, cx); this } @@ -2591,13 +2590,12 @@ impl Editor { } } - fn refresh_inlays(&mut self, cx: &mut ViewContext) { + fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { if self.mode != EditorMode::Full { return; } - let inlay_hint_settings = settings::get::(cx).inlay_hints; - if !inlay_hint_settings.enabled { + if !settings::get::(cx).inlay_hints.enabled { let to_remove = self.inlay_cache.clear(); self.display_map.update(cx, |display_map, cx| { display_map.splice_inlays(to_remove, Vec::new(), cx); @@ -2605,151 +2603,63 @@ impl Editor { return; } - struct InlayRequestKey { - buffer_path: PathBuf, - buffer_version: Global, - excerpt_id: ExcerptId, - } - - let multi_buffer = self.buffer(); - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let inlay_fetch_tasks = multi_buffer_snapshot - .excerpts() - .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { - let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?; - let buffer_id = buffer_snapshot.remote_id(); - let buffer_version = buffer_snapshot.version().clone(); - let buffer_handle = multi_buffer.read(cx).buffer(buffer_id); - let inlays_up_to_date = - self.inlay_cache - .inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id); - let key = InlayRequestKey { - buffer_path, - buffer_version, - excerpt_id, - }; - - // TODO kb split this into 2 different steps: - // 1. cache population - // 2. cache querying + hint filters on top (needs to store previous filter settings) - let task = cx.spawn(|editor, mut cx| async move { - if inlays_up_to_date { - anyhow::Ok((key, None)) - } else { - let Some(buffer_handle) = buffer_handle else { return Ok((key, Some(Vec::new()))) }; - let max_buffer_offset = cx.read(|cx| buffer_handle.read(cx).len()); - let excerpt_range = excerpt_range.context; - let query_start = excerpt_range.start.offset; - let query_end = excerpt_range.end.offset.min(max_buffer_offset); - let task = editor - .update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - project.query_inlay_hints_for_buffer( - buffer_handle, - query_start..query_end, - cx, - ) - }) - }) - }) - .context("inlays fecth task spawn")?; - - Ok((key, match task { - Some(task) => { - match task.await.context("inlays for buffer task")? { - Some(mut new_inlays) => { - let mut allowed_inlay_hint_types = Vec::new(); - if inlay_hint_settings.show_type_hints { - allowed_inlay_hint_types.push(Some(InlayHintKind::Type)); - } - if inlay_hint_settings.show_parameter_hints { - allowed_inlay_hint_types.push(Some(InlayHintKind::Parameter)); - } - if inlay_hint_settings.show_other_hints { - allowed_inlay_hint_types.push(None); - } - new_inlays.retain(|inlay| { - let inlay_offset = inlay.position.offset; - allowed_inlay_hint_types.contains(&inlay.kind) - && query_start <= inlay_offset && inlay_offset <= query_end - }); - Some(new_inlays) - }, - None => None, - } - - }, - None => Some(Vec::new()), - })) - } - }); - - Some(task) - }) - .collect::>(); - - cx.spawn(|editor, mut cx| async move { - let mut inlay_updates: HashMap< - PathBuf, - ( - Global, - HashMap>>, - ), - > = HashMap::default(); - let multi_buffer_snapshot = - editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; - - for task_result in futures::future::join_all(inlay_fetch_tasks).await { - match task_result { - Ok((request_key, response_inlays)) => { - let inlays_per_excerpt = HashMap::from_iter([( - request_key.excerpt_id, - response_inlays.map(|excerpt_inlays| { - excerpt_inlays.into_iter().fold( - OrderedByAnchorOffset::default(), - |mut ordered_inlays, inlay| { - let anchor = multi_buffer_snapshot.anchor_in_excerpt( - request_key.excerpt_id, - inlay.position, - ); - ordered_inlays.add(anchor, inlay); - ordered_inlays - }, - ) - }), - )]); - match inlay_updates.entry(request_key.buffer_path) { - hash_map::Entry::Occupied(mut o) => { - o.get_mut().1.extend(inlays_per_excerpt); - } - hash_map::Entry::Vacant(v) => { - v.insert((request_key.buffer_version, inlays_per_excerpt)); - } - } - } - Err(e) => error!("Failed to update inlays for buffer: {e:#}"), - } - } - - if !inlay_updates.is_empty() { + match reason { + InlayRefreshReason::Settings(new_settings) => { let InlaysUpdate { to_remove, to_insert, - } = editor.update(&mut cx, |editor, _| { - dbg!(editor.inlay_cache.update_inlays(inlay_updates)) - })?; - - editor.update(&mut cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(to_remove, to_insert, cx); - }); - })?; + } = self.inlay_cache.apply_settings(new_settings); + self.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, to_insert, cx); + }); } + InlayRefreshReason::Regular => { + let buffer_handle = self.buffer().clone(); + let inlay_fetch_ranges = buffer_handle + .read(cx) + .snapshot(cx) + .excerpts() + .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { + let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?; + let buffer_id = buffer_snapshot.remote_id(); + let buffer_version = buffer_snapshot.version().clone(); + let max_buffer_offset = buffer_snapshot.len(); + let excerpt_range = excerpt_range.context; + Some(QueryInlaysRange { + buffer_path, + buffer_id, + buffer_version, + excerpt_id, + excerpt_offset_range: excerpt_range.start.offset + ..excerpt_range.end.offset.min(max_buffer_offset), + }) + }) + .collect::>(); - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + cx.spawn(|editor, mut cx| async move { + let InlaysUpdate { + to_remove, + to_insert, + } = editor + .update(&mut cx, |editor, cx| { + editor.inlay_cache.fetch_inlays( + buffer_handle, + inlay_fetch_ranges.into_iter(), + cx, + ) + })? + .await + .context("inlay cache hint fetch")?; + + editor.update(&mut cx, |editor, cx| { + editor.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, to_insert, cx); + }); + }) + }) + .detach_and_log_err(cx); + } + } } fn trigger_on_type_formatting( @@ -5687,6 +5597,7 @@ impl Editor { } } + // TODO: Handle selections that cross excerpts // TODO: Handle selections that cross excerpts for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; @@ -7332,7 +7243,7 @@ impl Editor { }; if refresh_inlay_hints { - self.refresh_inlays(cx); + self.refresh_inlays(InlayRefreshReason::Regular, cx); } } @@ -7342,7 +7253,10 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); - self.refresh_inlays(cx); + self.refresh_inlays( + InlayRefreshReason::Settings(settings::get::(cx).inlay_hints), + cx, + ); } pub fn set_searchable(&mut self, searchable: bool) { diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index c563102544c4eba5ce8a96d1cf73f65a5f5f99c6..71c6f6e33711ceb5be2bff4da13e12c25dd4d101 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -1,18 +1,29 @@ use std::{ cmp, + ops::Range, path::{Path, PathBuf}, }; -use crate::{Anchor, ExcerptId}; +use crate::{editor_settings, Anchor, Editor, ExcerptId, MultiBuffer}; +use anyhow::Context; use clock::{Global, Local}; -use project::InlayHint; +use gpui::{ModelHandle, Task, ViewContext}; +use log::error; +use project::{InlayHint, InlayHintKind}; use util::post_inc; -use collections::{BTreeMap, HashMap}; +use collections::{hash_map, BTreeMap, HashMap, HashSet}; -#[derive(Clone, Debug, Default)] +#[derive(Debug, Copy, Clone)] +pub enum InlayRefreshReason { + Settings(editor_settings::InlayHints), + Regular, +} + +#[derive(Debug, Clone, Default)] pub struct InlayCache { inlays_per_buffer: HashMap, + allowed_hint_kinds: HashSet>, next_inlay_id: usize, } @@ -37,6 +48,10 @@ impl OrderedByAnchorOffset { fn into_ordered_elements(self) -> impl Iterator { self.0.into_values() } + + fn ordered_elements(&self) -> impl Iterator { + self.0.values() + } } impl Default for OrderedByAnchorOffset { @@ -54,14 +69,150 @@ struct BufferInlays { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct InlayId(pub usize); -#[derive(Debug)] +#[derive(Debug, Default)] pub struct InlaysUpdate { pub to_remove: Vec, pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } +impl InlaysUpdate { + fn merge(&mut self, other: Self) { + let mut new_to_remove = other.to_remove.iter().copied().collect::>(); + self.to_insert + .retain(|(inlay_id, _, _)| !new_to_remove.remove(&inlay_id)); + self.to_remove.extend(new_to_remove); + self.to_insert + .extend(other.to_insert.into_iter().filter(|(inlay_id, _, _)| { + !self + .to_remove + .iter() + .any(|removed_inlay_id| removed_inlay_id == inlay_id) + })); + } +} + +pub struct QueryInlaysRange { + pub buffer_id: u64, + pub buffer_path: PathBuf, + pub buffer_version: Global, + pub excerpt_id: ExcerptId, + pub excerpt_offset_range: Range, +} impl InlayCache { - pub fn inlays_up_to_date( + pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { + Self { + inlays_per_buffer: HashMap::default(), + allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), + next_inlay_id: 0, + } + } + + pub fn fetch_inlays( + &mut self, + multi_buffer: ModelHandle, + inlay_fetch_ranges: impl Iterator, + cx: &mut ViewContext, + ) -> Task> { + let mut inlay_fetch_tasks = Vec::new(); + for inlay_fetch_range in inlay_fetch_ranges { + let inlays_up_to_date = self.inlays_up_to_date( + &inlay_fetch_range.buffer_path, + &inlay_fetch_range.buffer_version, + inlay_fetch_range.excerpt_id, + ); + let task_multi_buffer = multi_buffer.clone(); + let task = cx.spawn(|editor, mut cx| async move { + if inlays_up_to_date { + anyhow::Ok((inlay_fetch_range, None)) + } else { + let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id)) + else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; + let task = editor + .update(&mut cx, |editor, cx| { + let max_buffer_offset = buffer_handle.read(cx).len(); + let excerpt_offset_range = &inlay_fetch_range.excerpt_offset_range; + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + project.query_inlay_hints_for_buffer( + buffer_handle, + excerpt_offset_range.start..excerpt_offset_range.end.min(max_buffer_offset), + cx, + ) + }) + }) + }) + .context("inlays fecth task spawn")?; + + Ok((inlay_fetch_range, match task { + Some(task) => task.await.context("inlays for buffer task")?, + None => Some(Vec::new()), + })) + } + }); + inlay_fetch_tasks.push(task); + } + + let final_task = cx.spawn(|editor, mut cx| async move { + let mut inlay_updates: HashMap< + PathBuf, + ( + Global, + HashMap, OrderedByAnchorOffset)>>, + ), + > = HashMap::default(); + let multi_buffer_snapshot = + editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; + + for task_result in futures::future::join_all(inlay_fetch_tasks).await { + match task_result { + Ok((request_key, response_inlays)) => { + let inlays_per_excerpt = HashMap::from_iter([( + request_key.excerpt_id, + response_inlays + .map(|excerpt_inlays| { + excerpt_inlays.into_iter().fold( + OrderedByAnchorOffset::default(), + |mut ordered_inlays, inlay| { + let anchor = multi_buffer_snapshot.anchor_in_excerpt( + request_key.excerpt_id, + inlay.position, + ); + ordered_inlays.add(anchor, inlay); + ordered_inlays + }, + ) + }) + .map(|inlays| (request_key.excerpt_offset_range, inlays)), + )]); + match inlay_updates.entry(request_key.buffer_path) { + hash_map::Entry::Occupied(mut o) => { + o.get_mut().1.extend(inlays_per_excerpt); + } + hash_map::Entry::Vacant(v) => { + v.insert((request_key.buffer_version, inlays_per_excerpt)); + } + } + } + Err(e) => error!("Failed to update inlays for buffer: {e:#}"), + } + } + + let updates = if !inlay_updates.is_empty() { + let inlays_update = editor.update(&mut cx, |editor, _| { + editor.inlay_cache.apply_fetch_inlays(inlay_updates) + })?; + inlays_update + } else { + InlaysUpdate::default() + }; + + anyhow::Ok(updates) + }); + + final_task + } + + fn inlays_up_to_date( &self, buffer_path: &Path, buffer_version: &Global, @@ -69,17 +220,17 @@ impl InlayCache { ) -> bool { let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version - || buffer_inlays.buffer_version.changed_since(buffer_version); + || buffer_inlays.buffer_version.changed_since(&buffer_version); buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) } - pub fn update_inlays( + fn apply_fetch_inlays( &mut self, - inlay_updates: HashMap< + fetched_inlays: HashMap< PathBuf, ( Global, - HashMap>>, + HashMap, OrderedByAnchorOffset)>>, ), >, ) -> InlaysUpdate { @@ -87,10 +238,17 @@ impl InlayCache { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - for (buffer_path, (buffer_version, new_buffer_inlays)) in inlay_updates { + for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays { match old_inlays.remove(&buffer_path) { Some(mut old_buffer_inlays) => { for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { + let (_, mut new_excerpt_inlays) = match new_excerpt_inlays { + Some((excerpt_offset_range, new_inlays)) => ( + excerpt_offset_range, + new_inlays.into_ordered_elements().fuse().peekable(), + ), + None => continue, + }; if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { continue; } @@ -99,12 +257,7 @@ impl InlayCache { .inlays_per_buffer .get_mut(&buffer_path) .expect("element expected: `old_inlays.remove` returned `Some`"); - let mut new_excerpt_inlays = match new_excerpt_inlays { - Some(new_inlays) => { - new_inlays.into_ordered_elements().fuse().peekable() - } - None => continue, - }; + if old_buffer_inlays .inlays_per_excerpts .remove(&excerpt_id) @@ -192,7 +345,7 @@ impl InlayCache { OrderedByAnchorOffset<(InlayId, InlayHint)>, > = HashMap::default(); for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { - if let Some(new_ordered_inlays) = new_ordered_inlays { + if let Some((_, new_ordered_inlays)) = new_ordered_inlays { for (new_anchor, new_inlay) in new_ordered_inlays.into_ordered_elements() { @@ -230,6 +383,49 @@ impl InlayCache { } } + pub fn apply_settings( + &mut self, + inlay_hint_settings: editor_settings::InlayHints, + ) -> InlaysUpdate { + let new_allowed_inlay_hint_types = allowed_inlay_hint_types(inlay_hint_settings); + + let new_allowed_hint_kinds = new_allowed_inlay_hint_types + .difference(&self.allowed_hint_kinds) + .copied() + .collect::>(); + let removed_hint_kinds = self + .allowed_hint_kinds + .difference(&new_allowed_inlay_hint_types) + .collect::>(); + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + for (anchor, (inlay_id, inlay_hint)) in self + .inlays_per_buffer + .iter() + .map(|(_, buffer_inlays)| { + buffer_inlays + .inlays_per_excerpts + .iter() + .map(|(_, excerpt_inlays)| excerpt_inlays.ordered_elements()) + .flatten() + }) + .flatten() + { + if removed_hint_kinds.contains(&inlay_hint.kind) { + to_remove.push(*inlay_id); + } else if new_allowed_hint_kinds.contains(&inlay_hint.kind) { + to_insert.push((*inlay_id, *anchor, inlay_hint.to_owned())); + } + } + + self.allowed_hint_kinds = new_allowed_hint_kinds; + + InlaysUpdate { + to_remove, + to_insert, + } + } + pub fn clear(&mut self) -> Vec { self.inlays_per_buffer .drain() @@ -248,3 +444,19 @@ impl InlayCache { .collect() } } + +fn allowed_inlay_hint_types( + inlay_hint_settings: editor_settings::InlayHints, +) -> HashSet> { + let mut new_allowed_inlay_hint_types = HashSet::default(); + if inlay_hint_settings.show_type_hints { + new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Type)); + } + if inlay_hint_settings.show_parameter_hints { + new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Parameter)); + } + if inlay_hint_settings.show_other_hints { + new_allowed_inlay_hint_types.insert(None); + } + new_allowed_inlay_hint_types +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5c08ff6b8249c813050762e87d7dfdc052028cf2..fb2cef9863934d1a00ff8d81721eeba1b5aae28e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -321,13 +321,13 @@ pub struct DiagnosticSummary { pub warning_count: usize, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { pub buffer: ModelHandle, pub range: Range, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlayHint { pub buffer_id: u64, pub position: Anchor, @@ -338,7 +338,7 @@ pub struct InlayHint { pub tooltip: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum InlayHintKind { Type, Parameter, @@ -370,32 +370,32 @@ impl InlayHint { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum InlayHintLabel { String(String), LabelParts(Vec), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlayHintLabelPart { pub value: String, pub tooltip: Option, pub location: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum InlayHintTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum InlayHintLabelPartTooltip { String(String), MarkupContent(MarkupContent), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MarkupContent { pub kind: String, pub value: String, @@ -4975,7 +4975,7 @@ impl Project { lsp_request, response, project, - buffer_handle, + buffer_handle.clone(), cx, ) .await; From b231fa47afcf557b5c902f5c344aa8e7f7ef2f41 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Jun 2023 13:51:23 +0300 Subject: [PATCH 090/169] Apply hints setings on startup --- crates/editor/src/editor.rs | 6 +++--- crates/editor/src/inlay_cache.rs | 31 +++++++++---------------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 22a2c985aeb49d462fc32d5a66025a57fb960e5b..3ab090578dcd0e6ace01e9388d7e3fa7eca382c2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_cache::{InlayCache, InlayRefreshReason, InlaysUpdate, QueryInlaysRange}; +use inlay_cache::{InlayCache, InlayRefreshReason, InlaySplice, QueryInlaysRange}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -2605,7 +2605,7 @@ impl Editor { match reason { InlayRefreshReason::Settings(new_settings) => { - let InlaysUpdate { + let InlaySplice { to_remove, to_insert, } = self.inlay_cache.apply_settings(new_settings); @@ -2637,7 +2637,7 @@ impl Editor { .collect::>(); cx.spawn(|editor, mut cx| async move { - let InlaysUpdate { + let InlaySplice { to_remove, to_insert, } = editor diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index 71c6f6e33711ceb5be2bff4da13e12c25dd4d101..d004f07491d27b5b28012db3c2bcab918c842924 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -70,25 +70,10 @@ struct BufferInlays { pub struct InlayId(pub usize); #[derive(Debug, Default)] -pub struct InlaysUpdate { +pub struct InlaySplice { pub to_remove: Vec, pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } -impl InlaysUpdate { - fn merge(&mut self, other: Self) { - let mut new_to_remove = other.to_remove.iter().copied().collect::>(); - self.to_insert - .retain(|(inlay_id, _, _)| !new_to_remove.remove(&inlay_id)); - self.to_remove.extend(new_to_remove); - self.to_insert - .extend(other.to_insert.into_iter().filter(|(inlay_id, _, _)| { - !self - .to_remove - .iter() - .any(|removed_inlay_id| removed_inlay_id == inlay_id) - })); - } -} pub struct QueryInlaysRange { pub buffer_id: u64, @@ -112,7 +97,7 @@ impl InlayCache { multi_buffer: ModelHandle, inlay_fetch_ranges: impl Iterator, cx: &mut ViewContext, - ) -> Task> { + ) -> Task> { let mut inlay_fetch_tasks = Vec::new(); for inlay_fetch_range in inlay_fetch_ranges { let inlays_up_to_date = self.inlays_up_to_date( @@ -203,7 +188,7 @@ impl InlayCache { })?; inlays_update } else { - InlaysUpdate::default() + InlaySplice::default() }; anyhow::Ok(updates) @@ -233,7 +218,7 @@ impl InlayCache { HashMap, OrderedByAnchorOffset)>>, ), >, - ) -> InlaysUpdate { + ) -> InlaySplice { let mut old_inlays = self.inlays_per_buffer.clone(); let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); @@ -377,7 +362,9 @@ impl InlayCache { } } - InlaysUpdate { + to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); + + InlaySplice { to_remove, to_insert, } @@ -386,7 +373,7 @@ impl InlayCache { pub fn apply_settings( &mut self, inlay_hint_settings: editor_settings::InlayHints, - ) -> InlaysUpdate { + ) -> InlaySplice { let new_allowed_inlay_hint_types = allowed_inlay_hint_types(inlay_hint_settings); let new_allowed_hint_kinds = new_allowed_inlay_hint_types @@ -420,7 +407,7 @@ impl InlayCache { self.allowed_hint_kinds = new_allowed_hint_kinds; - InlaysUpdate { + InlaySplice { to_remove, to_insert, } From 02e124cec46c1545e5cfeb83502c43965554d6a4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Jun 2023 17:48:46 +0300 Subject: [PATCH 091/169] Fix inlay map tests Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 235 +++++++++------------ crates/editor/src/multi_buffer/anchor.rs | 18 ++ 2 files changed, 114 insertions(+), 139 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 216436bb11d8a73f924b6be79da1315c1574ca7a..cdc06fc66b1f1ce0e37bc1e03dc752b8f5147d12 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -6,7 +6,7 @@ use super::{ TextHighlights, }; use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint}; -use collections::{BTreeMap, HashMap}; +use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; @@ -19,7 +19,8 @@ use text::Patch; pub struct InlayMap { snapshot: Mutex, - pub(super) inlays: HashMap, + inlays_by_id: HashMap, + inlays: Vec, } #[derive(Clone)] @@ -242,7 +243,8 @@ impl InlayMap { ( Self { snapshot: Mutex::new(snapshot.clone()), - inlays: HashMap::default(), + inlays_by_id: Default::default(), + inlays: Default::default(), }, snapshot, ) @@ -281,12 +283,7 @@ impl InlayMap { // Remove all the inlays and transforms contained by the edit. let old_start = cursor.start().1 + InlayOffset(suggestion_edit.old.start.0 - cursor.start().0 .0); - while suggestion_edit.old.end > cursor.end(&()).0 { - if let Some(Transform::Inlay(inlay)) = cursor.item() { - self.inlays.remove(&inlay.id); - } - cursor.next(&()); - } + cursor.seek(&suggestion_edit.old.end, Bias::Right, &()); let old_end = cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0); @@ -300,39 +297,66 @@ impl InlayMap { ..suggestion_snapshot.to_point(prefix_end), ), ); + let new_start = InlayOffset(new_transforms.summary().output.len); - // Leave all the inlays starting at the end of the edit if they have a left bias. - while let Some(Transform::Inlay(inlay)) = cursor.item() { - if inlay.position.bias() == Bias::Left { - new_transforms.push(Transform::Inlay(inlay.clone()), &()); - cursor.next(&()); - } else { + let start_point = suggestion_snapshot + .to_fold_point(suggestion_snapshot.to_point(suggestion_edit.new.start)) + .to_buffer_point(&suggestion_snapshot.fold_snapshot); + let start_ix = match self.inlays.binary_search_by(|probe| { + probe + .position + .to_point(&suggestion_snapshot.buffer_snapshot()) + .cmp(&start_point) + .then(std::cmp::Ordering::Greater) + }) { + Ok(ix) | Err(ix) => ix, + }; + + for inlay in &self.inlays[start_ix..] { + let buffer_point = inlay + .position + .to_point(suggestion_snapshot.buffer_snapshot()); + let fold_point = suggestion_snapshot + .fold_snapshot + .to_fold_point(buffer_point, Bias::Left); + let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point); + let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point); + if suggestion_offset > suggestion_edit.new.end { break; } + + let prefix_start = SuggestionOffset(new_transforms.summary().input.len); + let prefix_end = suggestion_offset; + push_isomorphic( + &mut new_transforms, + suggestion_snapshot.text_summary_for_range( + suggestion_snapshot.to_point(prefix_start) + ..suggestion_snapshot.to_point(prefix_end), + ), + ); + + if inlay + .position + .is_valid(suggestion_snapshot.buffer_snapshot()) + { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + } } - // Apply the edit. - let new_start = InlayOffset(new_transforms.summary().output.len); - let new_end = - InlayOffset(new_transforms.summary().output.len + suggestion_edit.new_len().0); - inlay_edits.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); + // Apply the rest of the edit. + let transform_start = SuggestionOffset(new_transforms.summary().input.len); push_isomorphic( &mut new_transforms, suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(suggestion_edit.new.start) + suggestion_snapshot.to_point(transform_start) ..suggestion_snapshot.to_point(suggestion_edit.new.end), ), ); - - // Push all the inlays starting at the end of the edit if they have a right bias. - while let Some(Transform::Inlay(inlay)) = cursor.item() { - debug_assert_eq!(inlay.position.bias(), Bias::Right); - new_transforms.push(Transform::Inlay(inlay.clone()), &()); - cursor.next(&()); - } + let new_end = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); // If the next edit doesn't intersect the current isomorphic transform, then // we can push its remainder. @@ -369,16 +393,25 @@ impl InlayMap { to_remove: Vec, to_insert: Vec<(InlayId, InlayProperties)>, ) -> (InlaySnapshot, Vec) { - let mut snapshot = self.snapshot.lock(); + let snapshot = self.snapshot.lock(); - let mut inlays = BTreeMap::new(); + let mut edits = BTreeSet::new(); for (id, properties) in to_insert { let inlay = Inlay { id, position: properties.position, text: properties.text.into(), }; - self.inlays.insert(inlay.id, inlay.clone()); + self.inlays_by_id.insert(inlay.id, inlay.clone()); + match self.inlays.binary_search_by(|probe| { + probe + .position + .cmp(&inlay.position, snapshot.buffer_snapshot()) + }) { + Ok(ix) | Err(ix) => { + self.inlays.insert(ix, inlay.clone()); + } + } let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot @@ -386,127 +419,49 @@ impl InlayMap { .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - inlays.insert( - (suggestion_point, inlay.position.bias(), inlay.id), - Some(inlay), - ); + let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); + edits.insert(suggestion_offset); } + self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); for inlay_id in to_remove { - if let Some(inlay) = self.inlays.remove(&inlay_id) { + if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot .suggestion_snapshot .fold_snapshot .to_fold_point(buffer_point, Bias::Left); let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - inlays.insert((suggestion_point, inlay.position.bias(), inlay.id), None); + let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); + edits.insert(suggestion_offset); } } - let mut inlay_edits = Patch::default(); - let mut new_transforms = SumTree::new(); - let mut cursor = snapshot - .transforms - .cursor::<(SuggestionPoint, (InlayOffset, InlayPoint))>(); - let mut inlays = inlays.into_iter().peekable(); - while let Some(((suggestion_point, bias, inlay_id), inlay_to_insert)) = inlays.next() { - new_transforms.push_tree(cursor.slice(&suggestion_point, Bias::Left, &()), &()); - - while let Some(transform) = cursor.item() { - match transform { - Transform::Isomorphic(_) => { - if suggestion_point >= cursor.end(&()).0 { - new_transforms.push(transform.clone(), &()); - cursor.next(&()); - } else { - break; - } - } - Transform::Inlay(inlay) => { - if (inlay.position.bias(), inlay.id) < (bias, inlay_id) { - new_transforms.push(transform.clone(), &()); - cursor.next(&()); - } else { - if inlay.id == inlay_id { - let new_start = InlayOffset(new_transforms.summary().output.len); - inlay_edits.push(Edit { - old: cursor.start().1 .0..cursor.end(&()).1 .0, - new: new_start..new_start, - }); - cursor.next(&()); - } - break; - } - } - } - } - - if let Some(inlay) = inlay_to_insert { - let prefix_suggestion_start = SuggestionPoint(new_transforms.summary().input.lines); - push_isomorphic( - &mut new_transforms, - snapshot - .suggestion_snapshot - .text_summary_for_range(prefix_suggestion_start..suggestion_point), - ); - - let new_start = InlayOffset(new_transforms.summary().output.len); - let new_end = InlayOffset(new_start.0 + inlay.text.len()); - if let Some(Transform::Isomorphic(_)) = cursor.item() { - let old_start = snapshot.to_offset(InlayPoint( - cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0), - )); - inlay_edits.push(Edit { - old: old_start..old_start, - new: new_start..new_end, - }); - - new_transforms.push(Transform::Inlay(inlay), &()); - - if inlays.peek().map_or(true, |((suggestion_point, _, _), _)| { - *suggestion_point >= cursor.end(&()).0 - }) { - let suffix_suggestion_end = cursor.end(&()).0; - push_isomorphic( - &mut new_transforms, - snapshot - .suggestion_snapshot - .text_summary_for_range(suggestion_point..suffix_suggestion_end), - ); - cursor.next(&()); - } - } else { - let old_start = cursor.start().1 .0; - inlay_edits.push(Edit { - old: old_start..old_start, - new: new_start..new_end, - }); - new_transforms.push(Transform::Inlay(inlay), &()); - } - } - } - - new_transforms.push_tree(cursor.suffix(&()), &()); - drop(cursor); - snapshot.transforms = new_transforms; - snapshot.version += 1; - snapshot.check_invariants(); - - (snapshot.clone(), inlay_edits.into_inner()) + let suggestion_snapshot = snapshot.suggestion_snapshot.clone(); + let suggestion_edits = edits + .into_iter() + .map(|offset| Edit { + old: offset..offset, + new: offset..offset, + }) + .collect(); + drop(snapshot); + self.sync(suggestion_snapshot, suggestion_edits) } #[cfg(any(test, feature = "test-support"))] pub fn randomly_mutate( &mut self, + next_inlay_id: &mut usize, rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec) { use rand::prelude::*; + use util::post_inc; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); let snapshot = self.snapshot.lock(); - for i in 0..rng.gen_range(1..=5) { + for _ in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { let buffer_snapshot = snapshot.buffer_snapshot(); let position = buffer_snapshot.random_byte_range(0, rng).start; @@ -522,16 +477,17 @@ impl InlayMap { text ); to_insert.push(( - InlayId(i), + InlayId(post_inc(next_inlay_id)), InlayProperties { position: buffer_snapshot.anchor_at(position, bias), text, }, )); } else { - to_remove.push(*self.inlays.keys().choose(rng).unwrap()); + to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap()); } } + log::info!("removing inlays: {:?}", to_remove); drop(snapshot); self.splice(to_remove, to_insert) @@ -965,8 +921,8 @@ mod tests { assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. - let (inlay_snapshot, _) = - inlay_map.splice::(inlay_map.inlays.keys().copied().collect(), Vec::new()); + let (inlay_snapshot, _) = inlay_map + .splice::(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } @@ -993,6 +949,7 @@ mod tests { let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let mut next_inlay_id = 0; for _ in 0..operations { let mut suggestion_edits = Patch::default(); @@ -1002,7 +959,7 @@ mod tests { let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=29 => { - let (snapshot, edits) = inlay_map.randomly_mutate(&mut rng); + let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); log::info!("mutated text: {:?}", snapshot.text()); inlay_edits = Patch::new(edits); } @@ -1041,9 +998,10 @@ mod tests { log::info!("suggestions text: {:?}", suggestion_snapshot.text()); log::info!("inlay text: {:?}", inlay_snapshot.text()); - let mut inlays = inlay_map + let inlays = inlay_map .inlays - .values() + .iter() + .filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) .map(|inlay| { let buffer_point = inlay.position.to_point(&buffer_snapshot); let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); @@ -1052,7 +1010,6 @@ mod tests { (suggestion_offset, inlay.clone()) }) .collect::>(); - inlays.sort_by_key(|(offset, inlay)| (*offset, inlay.position.bias(), inlay.id)); let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); for (offset, inlay) in inlays.into_iter().rev() { expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index b308927cbb1dca319dd629b152c2fd7295afbdfe..091196e8d0e1494621c4d37ccb5126f9ec7095f2 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -85,6 +85,24 @@ impl Anchor { { snapshot.summary_for_anchor(self) } + + pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool { + if *self == Anchor::min() || *self == Anchor::max() { + true + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + self.text_anchor.is_valid(&excerpt.buffer) + && self + .text_anchor + .cmp(&excerpt.range.context.start, &excerpt.buffer) + .is_ge() + && self + .text_anchor + .cmp(&excerpt.range.context.end, &excerpt.buffer) + .is_le() + } else { + false + } + } } impl ToOffset for Anchor { From bec9c26fa2256bfbaf10f3343c1cc56c00a84966 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Jun 2023 18:59:27 +0300 Subject: [PATCH 092/169] Fix more inlay_map corner cases and hangings Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 21 +++++++++++++++++++-- crates/editor/src/multi_buffer/anchor.rs | 13 ++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index cdc06fc66b1f1ce0e37bc1e03dc752b8f5147d12..9818ad0aab907ce4652573937716bc66782c9636 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -253,7 +253,7 @@ impl InlayMap { pub fn sync( &mut self, suggestion_snapshot: SuggestionSnapshot, - suggestion_edits: Vec, + mut suggestion_edits: Vec, ) -> (InlaySnapshot, Vec) { let mut snapshot = self.snapshot.lock(); @@ -262,6 +262,22 @@ impl InlayMap { new_snapshot.version += 1; } + if suggestion_snapshot + .buffer_snapshot() + .trailing_excerpt_update_count() + != snapshot + .suggestion_snapshot + .buffer_snapshot() + .trailing_excerpt_update_count() + { + if suggestion_edits.is_empty() { + suggestion_edits.push(Edit { + old: snapshot.suggestion_snapshot.len()..snapshot.suggestion_snapshot.len(), + new: suggestion_snapshot.len()..suggestion_snapshot.len(), + }); + } + } + let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::new(); let mut cursor = snapshot @@ -393,7 +409,8 @@ impl InlayMap { to_remove: Vec, to_insert: Vec<(InlayId, InlayProperties)>, ) -> (InlaySnapshot, Vec) { - let snapshot = self.snapshot.lock(); + let mut snapshot = self.snapshot.lock(); + snapshot.version += 1; let mut edits = BTreeSet::new(); for (id, properties) in to_insert { diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 091196e8d0e1494621c4d37ccb5126f9ec7095f2..1be4dc2dfb8512350f841ef44dc876c890c3c185 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -90,15 +90,10 @@ impl Anchor { if *self == Anchor::min() || *self == Anchor::max() { true } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { - self.text_anchor.is_valid(&excerpt.buffer) - && self - .text_anchor - .cmp(&excerpt.range.context.start, &excerpt.buffer) - .is_ge() - && self - .text_anchor - .cmp(&excerpt.range.context.end, &excerpt.buffer) - .is_le() + excerpt.contains(self) + && (self.text_anchor == excerpt.range.context.start + || self.text_anchor == excerpt.range.context.end + || self.text_anchor.is_valid(&excerpt.buffer)) } else { false } From 34c6d66d04c8644f892e41fcd68536ff9f2c8179 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 14 Jun 2023 19:59:01 +0300 Subject: [PATCH 093/169] Implement InlayBufferRows properly Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 64 +++++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 9818ad0aab907ce4652573937716bc66782c9636..06bf866ee8110dae92581e633ccecbdd20d27d69 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -125,7 +125,9 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint { #[derive(Clone)] pub struct InlayBufferRows<'a> { + transforms: Cursor<'a, Transform, (InlayPoint, SuggestionPoint)>, suggestion_rows: SuggestionBufferRows<'a>, + inlay_row: u32, } pub struct InlayChunks<'a> { @@ -211,7 +213,28 @@ impl<'a> Iterator for InlayBufferRows<'a> { type Item = Option; fn next(&mut self) -> Option { - self.suggestion_rows.next() + let mut buffer_row = None; + while let Some(transform) = self.transforms.item() { + match transform { + Transform::Inlay(inlay) => { + if self.inlay_row == self.transforms.end(&()).0.row() { + self.transforms.next(&()); + } else { + buffer_row = Some(None); + break; + } + } + Transform::Isomorphic(_) => { + buffer_row = Some(self.suggestion_rows.next().unwrap()); + break; + } + } + } + self.inlay_row += 1; + self.transforms + .seek(&InlayPoint::new(self.inlay_row, 0), Bias::Right, &()); + + buffer_row } } @@ -712,10 +735,20 @@ impl InlaySnapshot { summary } - // TODO kb copied from suggestion_snapshot pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { + let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + let inlay_point = InlayPoint::new(row, 0); + cursor.seek(&inlay_point, Bias::Right, &()); + let mut suggestion_point = cursor.start().1; + if let Some(Transform::Isomorphic(_)) = cursor.item() { + suggestion_point.0 += inlay_point.0 - cursor.start().0 .0; + } + InlayBufferRows { - suggestion_rows: self.suggestion_snapshot.buffer_rows(row), + transforms: cursor, + inlay_row: inlay_point.row(), + suggestion_row, + suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_point.row()), } } @@ -1032,18 +1065,19 @@ mod tests { expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); } assert_eq!(inlay_snapshot.text(), expected_text.to_string()); - // TODO kb !!! - // let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::>(); - // for row_start in 0..expected_buffer_rows.len() { - // assert_eq!( - // inlay_snapshot - // .buffer_rows(row_start as u32) - // .collect::>(), - // &expected_buffer_rows[row_start..], - // "incorrect buffer rows starting at {}", - // row_start - // ); - // } + + let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::>(); + dbg!(&expected_buffer_rows); + for row_start in 0..expected_buffer_rows.len() { + assert_eq!( + inlay_snapshot + .buffer_rows(row_start as u32) + .collect::>(), + &expected_buffer_rows[row_start..], + "incorrect buffer rows starting at {}", + row_start + ); + } for _ in 0..5 { let mut end = rng.gen_range(0..=inlay_snapshot.len().0); From 89137e2e836f3d48ac8eae0f0b21a14bebc9f3ce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 15 Jun 2023 09:30:19 +0200 Subject: [PATCH 094/169] Fix `InlayMap::buffer_rows` --- crates/editor/src/display_map/inlay_map.rs | 100 +++++++++++++++------ 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 06bf866ee8110dae92581e633ccecbdd20d27d69..6a511f38530793d14f72e80c5fdddb43aff97827 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -213,28 +213,20 @@ impl<'a> Iterator for InlayBufferRows<'a> { type Item = Option; fn next(&mut self) -> Option { - let mut buffer_row = None; - while let Some(transform) = self.transforms.item() { - match transform { - Transform::Inlay(inlay) => { - if self.inlay_row == self.transforms.end(&()).0.row() { - self.transforms.next(&()); - } else { - buffer_row = Some(None); - break; - } - } - Transform::Isomorphic(_) => { - buffer_row = Some(self.suggestion_rows.next().unwrap()); - break; - } + let buffer_row = if self.inlay_row == 0 { + self.suggestion_rows.next().unwrap() + } else { + match self.transforms.item()? { + Transform::Inlay(_) => None, + Transform::Isomorphic(_) => self.suggestion_rows.next().unwrap(), } - } + }; + self.inlay_row += 1; self.transforms - .seek(&InlayPoint::new(self.inlay_row, 0), Bias::Right, &()); + .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &()); - buffer_row + Some(buffer_row) } } @@ -418,6 +410,9 @@ impl InlayMap { } new_transforms.push_tree(cursor.suffix(&()), &()); + if new_transforms.first().is_none() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); + } new_snapshot.transforms = new_transforms; new_snapshot.suggestion_snapshot = suggestion_snapshot; new_snapshot.check_invariants(); @@ -738,17 +733,28 @@ impl InlaySnapshot { pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); let inlay_point = InlayPoint::new(row, 0); - cursor.seek(&inlay_point, Bias::Right, &()); + cursor.seek(&inlay_point, Bias::Left, &()); + let mut suggestion_point = cursor.start().1; - if let Some(Transform::Isomorphic(_)) = cursor.item() { - suggestion_point.0 += inlay_point.0 - cursor.start().0 .0; - } + let suggestion_row = if row == 0 { + 0 + } else { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + suggestion_point.0 += inlay_point.0 - cursor.start().0 .0; + suggestion_point.row() + } + _ => cmp::min( + suggestion_point.row() + 1, + self.suggestion_snapshot.max_point().row(), + ), + } + }; InlayBufferRows { transforms: cursor, inlay_row: inlay_point.row(), - suggestion_row, - suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_point.row()), + suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_row), } } @@ -976,6 +982,47 @@ mod tests { assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } + #[gpui::test] + fn test_buffer_rows(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); + let (_, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); + let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![ + ( + InlayId(0), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(0), + text: "|123|\n", + }, + ), + ( + InlayId(1), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(4), + text: "|456|", + }, + ), + ( + InlayId(1), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(7), + text: "\n|567|\n", + }, + ), + ], + ); + assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); + assert_eq!( + inlay_snapshot.buffer_rows(0).collect::>(), + vec![Some(0), None, Some(1), None, None, Some(2)] + ); + } + #[gpui::test(iterations = 100)] fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) { init_test(cx); @@ -1067,7 +1114,10 @@ mod tests { assert_eq!(inlay_snapshot.text(), expected_text.to_string()); let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::>(); - dbg!(&expected_buffer_rows); + assert_eq!( + expected_buffer_rows.len() as u32, + expected_text.max_point().row + 1 + ); for row_start in 0..expected_buffer_rows.len() { assert_eq!( inlay_snapshot From 8cdf1a0faff7d650f8711c36c2d87ad19baf84f7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 15 Jun 2023 11:24:25 +0300 Subject: [PATCH 095/169] Switch over to inlay map for Copilot suggestions Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 61 ++--------- crates/editor/src/display_map/inlay_map.rs | 47 ++++----- crates/editor/src/editor.rs | 114 ++++++++++++++++----- crates/editor/src/inlay_cache.rs | 13 +++ 4 files changed, 128 insertions(+), 107 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5d2207d1f9cbd8afdba5f433a26588b5c94eef58..3a1288248eb3eafd6df32602ebf3336a0e9e8633 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -6,12 +6,12 @@ mod tab_map; mod wrap_map; use crate::{ - display_map::inlay_map::InlayProperties, inlay_cache::InlayId, Anchor, AnchorRangeExt, - MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + inlay_cache::{Inlay, InlayId, InlayProperties}, + Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; -use fold_map::{FoldMap, FoldOffset}; +use fold_map::FoldMap; use gpui::{ color::Color, fonts::{FontId, HighlightStyle}, @@ -26,6 +26,7 @@ pub use suggestion_map::Suggestion; use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; use tab_map::TabMap; +use text::Rope; use wrap_map::WrapMap; pub use block_map::{ @@ -244,33 +245,6 @@ impl DisplayMap { self.text_highlights.remove(&Some(type_id)) } - pub fn has_suggestion(&self) -> bool { - self.suggestion_map.has_suggestion() - } - - pub fn replace_suggestion( - &mut self, - new_suggestion: Option>, - cx: &mut ModelContext, - ) -> Option> - where - T: ToPoint, - { - let snapshot = self.buffer.read(cx).snapshot(cx); - let edits = self.buffer_subscription.consume().into_inner(); - let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits, old_suggestion) = - self.suggestion_map.replace(new_suggestion, snapshot, edits); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); - let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); - let (snapshot, edits) = self - .wrap_map - .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.read(snapshot, edits); - old_suggestion - } - pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_font(font_id, font_size, cx)) @@ -285,10 +259,10 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn splice_inlays( + pub fn splice_inlays>( &mut self, to_remove: Vec, - to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, + to_insert: Vec<(InlayId, InlayProperties)>, cx: &mut ModelContext, ) { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); @@ -303,28 +277,7 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let new_inlays: Vec<(InlayId, InlayProperties)> = to_insert - .into_iter() - .map(|(inlay_id, hint_anchor, hint)| { - let mut text = hint.text(); - // TODO kb styling instead? - if hint.padding_right { - text.push(' '); - } - if hint.padding_left { - text.insert(0, ' '); - } - - ( - inlay_id, - InlayProperties { - position: hint_anchor.bias_left(&buffer_snapshot), - text, - }, - ) - }) - .collect(); - let (snapshot, edits) = self.inlay_map.splice(to_remove, new_inlays); + let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 6a511f38530793d14f72e80c5fdddb43aff97827..5284c0bd80e691f009981a091b6ac8465795ff9b 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -5,7 +5,10 @@ use super::{ }, TextHighlights, }; -use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint}; +use crate::{ + inlay_cache::{Inlay, InlayId, InlayProperties}, + MultiBufferSnapshot, ToPoint, +}; use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; @@ -140,19 +143,6 @@ pub struct InlayChunks<'a> { highlight_style: Option, } -#[derive(Debug, Clone)] -pub struct Inlay { - pub(super) id: InlayId, - pub(super) position: Anchor, - pub(super) text: Rope, -} - -#[derive(Debug, Clone)] -pub struct InlayProperties { - pub position: Anchor, - pub text: T, -} - impl<'a> Iterator for InlayChunks<'a> { type Item = Chunk<'a>; @@ -431,6 +421,21 @@ impl InlayMap { snapshot.version += 1; let mut edits = BTreeSet::new(); + + self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); + for inlay_id in to_remove { + if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { + let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); + let fold_point = snapshot + .suggestion_snapshot + .fold_snapshot + .to_fold_point(buffer_point, Bias::Left); + let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); + let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); + edits.insert(suggestion_offset); + } + } + for (id, properties) in to_insert { let inlay = Inlay { id, @@ -458,20 +463,6 @@ impl InlayMap { edits.insert(suggestion_offset); } - self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); - for inlay_id in to_remove { - if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { - let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); - let fold_point = snapshot - .suggestion_snapshot - .fold_snapshot - .to_fold_point(buffer_point, Bias::Left); - let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); - edits.insert(suggestion_offset); - } - } - let suggestion_snapshot = snapshot.suggestion_snapshot.clone(); let suggestion_edits = edits .into_iter() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3ab090578dcd0e6ace01e9388d7e3fa7eca382c2..33bf89e45272699592c5c3e4ceb68cc6b34084cd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,7 +54,9 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_cache::{InlayCache, InlayRefreshReason, InlaySplice, QueryInlaysRange}; +use inlay_cache::{ + Inlay, InlayCache, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, QueryInlaysRange, +}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -95,6 +97,7 @@ use std::{ time::{Duration, Instant}, }; pub use sum_tree::Bias; +use text::Rope; use theme::{DiagnosticStyle, Theme, ThemeSettings}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace}; @@ -1061,6 +1064,7 @@ pub struct CopilotState { cycled: bool, completions: Vec, active_completion_index: usize, + suggestion: Option, } impl Default for CopilotState { @@ -1072,6 +1076,7 @@ impl Default for CopilotState { completions: Default::default(), active_completion_index: 0, cycled: false, + suggestion: None, } } } @@ -2597,9 +2602,7 @@ impl Editor { if !settings::get::(cx).inlay_hints.enabled { let to_remove = self.inlay_cache.clear(); - self.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(to_remove, Vec::new(), cx); - }); + self.splice_inlay_hints(to_remove, Vec::new(), cx); return; } @@ -2609,9 +2612,7 @@ impl Editor { to_remove, to_insert, } = self.inlay_cache.apply_settings(new_settings); - self.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(to_remove, to_insert, cx); - }); + self.splice_inlay_hints(to_remove, to_insert, cx); } InlayRefreshReason::Regular => { let buffer_handle = self.buffer().clone(); @@ -2652,9 +2653,7 @@ impl Editor { .context("inlay cache hint fetch")?; editor.update(&mut cx, |editor, cx| { - editor.display_map.update(cx, |display_map, cx| { - display_map.splice_inlays(to_remove, to_insert, cx); - }); + editor.splice_inlay_hints(to_remove, to_insert, cx) }) }) .detach_and_log_err(cx); @@ -2662,6 +2661,40 @@ impl Editor { } } + fn splice_inlay_hints( + &self, + to_remove: Vec, + to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, + cx: &mut ViewContext, + ) { + let buffer = self.buffer.read(cx).read(cx); + let new_inlays: Vec<(InlayId, InlayProperties)> = to_insert + .into_iter() + .map(|(inlay_id, hint_anchor, hint)| { + let mut text = hint.text(); + // TODO kb styling instead? + if hint.padding_right { + text.push(' '); + } + if hint.padding_left { + text.insert(0, ' '); + } + + ( + inlay_id, + InlayProperties { + position: hint_anchor.bias_left(&buffer), + text, + }, + ) + }) + .collect(); + drop(buffer); + self.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, new_inlays, cx); + }); + } + fn trigger_on_type_formatting( &self, input: String, @@ -3312,10 +3345,7 @@ impl Editor { } fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { - if let Some(suggestion) = self - .display_map - .update(cx, |map, cx| map.replace_suggestion::(None, cx)) - { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { if let Some((copilot, completion)) = Copilot::global(cx).zip(self.copilot_state.active_completion()) { @@ -3334,7 +3364,7 @@ impl Editor { } fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { - if self.has_active_copilot_suggestion(cx) { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { copilot .update(cx, |copilot, cx| { @@ -3345,8 +3375,9 @@ impl Editor { self.report_copilot_event(None, false, cx) } - self.display_map - .update(cx, |map, cx| map.replace_suggestion::(None, cx)); + self.display_map.update(cx, |map, cx| { + map.splice_inlays::<&str>(vec![suggestion.id], Vec::new(), cx) + }); cx.notify(); true } else { @@ -3367,7 +3398,26 @@ impl Editor { } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { - self.display_map.read(cx).has_suggestion() + if let Some(suggestion) = self.copilot_state.suggestion.as_ref() { + let buffer = self.buffer.read(cx).read(cx); + suggestion.position.is_valid(&buffer) + } else { + false + } + } + + fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext) -> Option { + let suggestion = self.copilot_state.suggestion.take()?; + self.display_map.update(cx, |map, cx| { + map.splice_inlays::<&str>(vec![suggestion.id], Default::default(), cx); + }); + let buffer = self.buffer.read(cx).read(cx); + + if suggestion.position.is_valid(&buffer) { + Some(suggestion) + } else { + None + } } fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext) { @@ -3384,14 +3434,28 @@ impl Editor { .copilot_state .text_for_active_completion(cursor, &snapshot) { + let text = Rope::from(text); + let mut to_remove = Vec::new(); + if let Some(suggestion) = self.copilot_state.suggestion.take() { + to_remove.push(suggestion.id); + } + + let to_insert = vec![( + // TODO kb check how can I get the unique id for the suggestion + // Move the generation of the id inside the map + InlayId(usize::MAX), + InlayProperties { + position: cursor, + text: text.clone(), + }, + )]; self.display_map.update(cx, move |map, cx| { - map.replace_suggestion( - Some(Suggestion { - position: cursor, - text: text.trim_end().into(), - }), - cx, - ) + map.splice_inlays(to_remove, to_insert, cx) + }); + self.copilot_state.suggestion = Some(Inlay { + id: InlayId(usize::MAX), + position: cursor, + text, }); cx.notify(); } else { diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index d004f07491d27b5b28012db3c2bcab918c842924..6cb38958793c1da7ddd63404f6d6386adc4d20e4 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -14,6 +14,19 @@ use util::post_inc; use collections::{hash_map, BTreeMap, HashMap, HashSet}; +#[derive(Debug, Clone)] +pub struct Inlay { + pub id: InlayId, + pub position: Anchor, + pub text: text::Rope, +} + +#[derive(Debug, Clone)] +pub struct InlayProperties { + pub position: Anchor, + pub text: T, +} + #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { Settings(editor_settings::InlayHints), From d2fef07782abee95a2d4ce72832bc571c21477f2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 15 Jun 2023 11:50:01 +0300 Subject: [PATCH 096/169] Remove the SuggestionMap Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 29 +- crates/editor/src/display_map/block_map.rs | 29 +- crates/editor/src/display_map/fold_map.rs | 20 +- crates/editor/src/display_map/inlay_map.rs | 348 +++++++++------------ crates/editor/src/display_map/tab_map.rs | 40 +-- crates/editor/src/display_map/wrap_map.rs | 43 +-- 6 files changed, 201 insertions(+), 308 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 3a1288248eb3eafd6df32602ebf3336a0e9e8633..f7660ad2b31d1781763932a65cad91fb89824cbb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,12 +1,11 @@ mod block_map; mod fold_map; mod inlay_map; -mod suggestion_map; mod tab_map; mod wrap_map; use crate::{ - inlay_cache::{Inlay, InlayId, InlayProperties}, + inlay_cache::{InlayId, InlayProperties}, Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; pub use block_map::{BlockMap, BlockPoint}; @@ -22,8 +21,6 @@ use language::{ language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, }; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; -pub use suggestion_map::Suggestion; -use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; use tab_map::TabMap; use text::Rope; @@ -50,7 +47,6 @@ pub struct DisplayMap { buffer: ModelHandle, buffer_subscription: BufferSubscription, fold_map: FoldMap, - suggestion_map: SuggestionMap, inlay_map: InlayMap, tab_map: TabMap, wrap_map: ModelHandle, @@ -77,7 +73,6 @@ impl DisplayMap { let tab_size = Self::tab_size(&buffer, cx); let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (suggestion_map, snapshot) = SuggestionMap::new(snapshot); let (inlay_map, snapshot) = InlayMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); @@ -87,7 +82,6 @@ impl DisplayMap { buffer, buffer_subscription, fold_map, - suggestion_map, inlay_map, tab_map, wrap_map, @@ -101,8 +95,7 @@ impl DisplayMap { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); - let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); - let (inlay_snapshot, edits) = self.inlay_map.sync(suggestion_snapshot.clone(), edits); + let (inlay_snapshot, edits) = self.inlay_map.sync(fold_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); let (tab_snapshot, edits) = self.tab_map.sync(inlay_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self @@ -113,7 +106,6 @@ impl DisplayMap { DisplaySnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(cx), fold_snapshot, - suggestion_snapshot, inlay_snapshot, tab_snapshot, wrap_snapshot, @@ -141,7 +133,6 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -149,7 +140,6 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -168,7 +158,6 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -176,7 +165,6 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -194,7 +182,6 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -213,7 +200,6 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -269,7 +255,6 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(buffer_snapshot.clone(), edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -302,7 +287,6 @@ impl DisplayMap { pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, fold_snapshot: fold_map::FoldSnapshot, - suggestion_snapshot: suggestion_map::SuggestionSnapshot, inlay_snapshot: inlay_map::InlaySnapshot, tab_snapshot: tab_map::TabSnapshot, wrap_snapshot: wrap_map::WrapSnapshot, @@ -380,8 +364,7 @@ impl DisplaySnapshot { fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { let fold_point = self.fold_snapshot.to_fold_point(point, bias); - let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - let inlay_point = self.inlay_snapshot.to_inlay_point(suggestion_point); + let inlay_point = self.inlay_snapshot.to_inlay_point(fold_point); let tab_point = self.tab_snapshot.to_tab_point(inlay_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); @@ -393,8 +376,7 @@ impl DisplaySnapshot { let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, bias).0; - let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point); - let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); + let fold_point = self.inlay_snapshot.to_fold_point(inlay_point); fold_point.to_buffer_point(&self.fold_snapshot) } @@ -808,8 +790,7 @@ impl DisplayPoint { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let inlay_point = map.tab_snapshot.to_inlay_point(tab_point, bias).0; - let suggestion_point = map.inlay_snapshot.to_suggestion_point(inlay_point); - let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); + let fold_point = map.inlay_snapshot.to_fold_point(inlay_point); fold_point.to_buffer_offset(&map.fold_snapshot) } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 85d5275dd312a75ecab27fc04428253dcead5ef1..3bb8ccc2ac071b5b09e06ac345b23aa9466871ca 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -990,7 +990,6 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { mod tests { use super::*; use crate::display_map::inlay_map::InlayMap; - use crate::display_map::suggestion_map::SuggestionMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; @@ -1032,8 +1031,7 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot); let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1179,9 +1177,7 @@ mod tests { let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot, subscription.consume().into_inner()); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -1209,8 +1205,7 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1282,8 +1277,7 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot); let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); @@ -1339,10 +1333,7 @@ mod tests { let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), vec![]); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -1366,10 +1357,7 @@ mod tests { let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), vec![]); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -1391,10 +1379,7 @@ mod tests { } let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 36cfeba4a13d0ab4d8f0aba67a3a4505bd8e8136..5e4eeca454814f44ff77cfd2dfdacd9c3fbd5a6b 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -11,7 +11,7 @@ use std::{ any::TypeId, cmp::{self, Ordering}, iter::{self, Peekable}, - ops::{Range, Sub}, + ops::{Add, AddAssign, Range, Sub}, sync::atomic::{AtomicUsize, Ordering::SeqCst}, vec, }; @@ -508,6 +508,10 @@ impl FoldSnapshot { self.folds.items(&self.buffer_snapshot).len() } + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output.clone() + } + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); @@ -1170,6 +1174,20 @@ impl FoldOffset { } } +impl Add for FoldOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FoldOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + impl Sub for FoldOffset { type Output = Self; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 5284c0bd80e691f009981a091b6ac8465795ff9b..615d08fa87640fc824a2149d59b58d84452ef279 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,8 +1,5 @@ use super::{ - suggestion_map::{ - SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint, - SuggestionSnapshot, - }, + fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, TextHighlights, }; use crate::{ @@ -29,7 +26,7 @@ pub struct InlayMap { #[derive(Clone)] pub struct InlaySnapshot { // TODO kb merge these two together - pub suggestion_snapshot: SuggestionSnapshot, + pub fold_snapshot: FoldSnapshot, transforms: SumTree, pub version: usize, } @@ -105,7 +102,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionOffset { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += &summary.input.len; } @@ -120,7 +117,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += &summary.input.lines; } @@ -128,15 +125,15 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint { #[derive(Clone)] pub struct InlayBufferRows<'a> { - transforms: Cursor<'a, Transform, (InlayPoint, SuggestionPoint)>, - suggestion_rows: SuggestionBufferRows<'a>, + transforms: Cursor<'a, Transform, (InlayPoint, FoldPoint)>, + fold_rows: FoldBufferRows<'a>, inlay_row: u32, } pub struct InlayChunks<'a> { - transforms: Cursor<'a, Transform, (InlayOffset, SuggestionOffset)>, - suggestion_chunks: SuggestionChunks<'a>, - suggestion_chunk: Option>, + transforms: Cursor<'a, Transform, (InlayOffset, FoldOffset)>, + fold_chunks: FoldChunks<'a>, + fold_chunk: Option>, inlay_chunks: Option>, output_offset: InlayOffset, max_output_offset: InlayOffset, @@ -154,10 +151,10 @@ impl<'a> Iterator for InlayChunks<'a> { let chunk = match self.transforms.item()? { Transform::Isomorphic(_) => { let chunk = self - .suggestion_chunk - .get_or_insert_with(|| self.suggestion_chunks.next().unwrap()); + .fold_chunk + .get_or_insert_with(|| self.fold_chunks.next().unwrap()); if chunk.text.is_empty() { - *chunk = self.suggestion_chunks.next().unwrap(); + *chunk = self.fold_chunks.next().unwrap(); } let (prefix, suffix) = chunk.text.split_at(cmp::min( @@ -204,11 +201,11 @@ impl<'a> Iterator for InlayBufferRows<'a> { fn next(&mut self) -> Option { let buffer_row = if self.inlay_row == 0 { - self.suggestion_rows.next().unwrap() + self.fold_rows.next().unwrap() } else { match self.transforms.item()? { Transform::Inlay(_) => None, - Transform::Isomorphic(_) => self.suggestion_rows.next().unwrap(), + Transform::Isomorphic(_) => self.fold_rows.next().unwrap(), } }; @@ -235,12 +232,12 @@ impl InlayPoint { } impl InlayMap { - pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) { + pub fn new(fold_snapshot: FoldSnapshot) -> (Self, InlaySnapshot) { let snapshot = InlaySnapshot { - suggestion_snapshot: suggestion_snapshot.clone(), + fold_snapshot: fold_snapshot.clone(), version: 0, transforms: SumTree::from_item( - Transform::Isomorphic(suggestion_snapshot.text_summary()), + Transform::Isomorphic(fold_snapshot.text_summary()), &(), ), }; @@ -257,45 +254,40 @@ impl InlayMap { pub fn sync( &mut self, - suggestion_snapshot: SuggestionSnapshot, - mut suggestion_edits: Vec, + fold_snapshot: FoldSnapshot, + mut fold_edits: Vec, ) -> (InlaySnapshot, Vec) { let mut snapshot = self.snapshot.lock(); let mut new_snapshot = snapshot.clone(); - if new_snapshot.suggestion_snapshot.version != suggestion_snapshot.version { + if new_snapshot.fold_snapshot.version != fold_snapshot.version { new_snapshot.version += 1; } - if suggestion_snapshot + if fold_snapshot .buffer_snapshot() .trailing_excerpt_update_count() != snapshot - .suggestion_snapshot + .fold_snapshot .buffer_snapshot() .trailing_excerpt_update_count() { - if suggestion_edits.is_empty() { - suggestion_edits.push(Edit { - old: snapshot.suggestion_snapshot.len()..snapshot.suggestion_snapshot.len(), - new: suggestion_snapshot.len()..suggestion_snapshot.len(), + if fold_edits.is_empty() { + fold_edits.push(Edit { + old: snapshot.fold_snapshot.len()..snapshot.fold_snapshot.len(), + new: fold_snapshot.len()..fold_snapshot.len(), }); } } let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::new(); - let mut cursor = snapshot - .transforms - .cursor::<(SuggestionOffset, InlayOffset)>(); - let mut suggestion_edits_iter = suggestion_edits.iter().peekable(); - while let Some(suggestion_edit) = suggestion_edits_iter.next() { - new_transforms.push_tree( - cursor.slice(&suggestion_edit.old.start, Bias::Left, &()), - &(), - ); + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); + let mut fold_edits_iter = fold_edits.iter().peekable(); + while let Some(fold_edit) = fold_edits_iter.next() { + new_transforms.push_tree(cursor.slice(&fold_edit.old.start, Bias::Left, &()), &()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { - if cursor.end(&()).0 == suggestion_edit.old.start { + if cursor.end(&()).0 == fold_edit.old.start { new_transforms.push(Transform::Isomorphic(transform.clone()), &()); cursor.next(&()); } @@ -303,30 +295,30 @@ impl InlayMap { // Remove all the inlays and transforms contained by the edit. let old_start = - cursor.start().1 + InlayOffset(suggestion_edit.old.start.0 - cursor.start().0 .0); - cursor.seek(&suggestion_edit.old.end, Bias::Right, &()); - let old_end = - cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0); + cursor.start().1 + InlayOffset(fold_edit.old.start.0 - cursor.start().0 .0); + cursor.seek(&fold_edit.old.end, Bias::Right, &()); + let old_end = cursor.start().1 + InlayOffset(fold_edit.old.end.0 - cursor.start().0 .0); // Push the unchanged prefix. - let prefix_start = SuggestionOffset(new_transforms.summary().input.len); - let prefix_end = suggestion_edit.new.start; + let prefix_start = FoldOffset(new_transforms.summary().input.len); + let prefix_end = fold_edit.new.start; push_isomorphic( &mut new_transforms, - suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(prefix_start) - ..suggestion_snapshot.to_point(prefix_end), + fold_snapshot.text_summary_for_range( + prefix_start.to_point(&fold_snapshot)..prefix_end.to_point(&fold_snapshot), ), ); let new_start = InlayOffset(new_transforms.summary().output.len); - let start_point = suggestion_snapshot - .to_fold_point(suggestion_snapshot.to_point(suggestion_edit.new.start)) - .to_buffer_point(&suggestion_snapshot.fold_snapshot); + let start_point = fold_edit + .new + .start + .to_point(&fold_snapshot) + .to_buffer_point(&fold_snapshot); let start_ix = match self.inlays.binary_search_by(|probe| { probe .position - .to_point(&suggestion_snapshot.buffer_snapshot()) + .to_point(&fold_snapshot.buffer_snapshot()) .cmp(&start_point) .then(std::cmp::Ordering::Greater) }) { @@ -334,43 +326,34 @@ impl InlayMap { }; for inlay in &self.inlays[start_ix..] { - let buffer_point = inlay - .position - .to_point(suggestion_snapshot.buffer_snapshot()); - let fold_point = suggestion_snapshot - .fold_snapshot - .to_fold_point(buffer_point, Bias::Left); - let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point); - let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point); - if suggestion_offset > suggestion_edit.new.end { + let buffer_point = inlay.position.to_point(fold_snapshot.buffer_snapshot()); + let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); + let fold_offset = fold_point.to_offset(&fold_snapshot); + if fold_offset > fold_edit.new.end { break; } - let prefix_start = SuggestionOffset(new_transforms.summary().input.len); - let prefix_end = suggestion_offset; + let prefix_start = FoldOffset(new_transforms.summary().input.len); + let prefix_end = fold_offset; push_isomorphic( &mut new_transforms, - suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(prefix_start) - ..suggestion_snapshot.to_point(prefix_end), + fold_snapshot.text_summary_for_range( + prefix_start.to_point(&fold_snapshot)..prefix_end.to_point(&fold_snapshot), ), ); - if inlay - .position - .is_valid(suggestion_snapshot.buffer_snapshot()) - { + if inlay.position.is_valid(fold_snapshot.buffer_snapshot()) { new_transforms.push(Transform::Inlay(inlay.clone()), &()); } } // Apply the rest of the edit. - let transform_start = SuggestionOffset(new_transforms.summary().input.len); + let transform_start = FoldOffset(new_transforms.summary().input.len); push_isomorphic( &mut new_transforms, - suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(transform_start) - ..suggestion_snapshot.to_point(suggestion_edit.new.end), + fold_snapshot.text_summary_for_range( + transform_start.to_point(&fold_snapshot) + ..fold_edit.new.end.to_point(&fold_snapshot), ), ); let new_end = InlayOffset(new_transforms.summary().output.len); @@ -381,18 +364,17 @@ impl InlayMap { // If the next edit doesn't intersect the current isomorphic transform, then // we can push its remainder. - if suggestion_edits_iter + if fold_edits_iter .peek() .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) { - let transform_start = SuggestionOffset(new_transforms.summary().input.len); - let transform_end = - suggestion_edit.new.end + (cursor.end(&()).0 - suggestion_edit.old.end); + let transform_start = FoldOffset(new_transforms.summary().input.len); + let transform_end = fold_edit.new.end + (cursor.end(&()).0 - fold_edit.old.end); push_isomorphic( &mut new_transforms, - suggestion_snapshot.text_summary_for_range( - suggestion_snapshot.to_point(transform_start) - ..suggestion_snapshot.to_point(transform_end), + fold_snapshot.text_summary_for_range( + transform_start.to_point(&fold_snapshot) + ..transform_end.to_point(&fold_snapshot), ), ); cursor.next(&()); @@ -404,7 +386,7 @@ impl InlayMap { new_transforms.push(Transform::Isomorphic(Default::default()), &()); } new_snapshot.transforms = new_transforms; - new_snapshot.suggestion_snapshot = suggestion_snapshot; + new_snapshot.fold_snapshot = fold_snapshot; new_snapshot.check_invariants(); drop(cursor); @@ -427,12 +409,10 @@ impl InlayMap { if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot - .suggestion_snapshot .fold_snapshot .to_fold_point(buffer_point, Bias::Left); - let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); - edits.insert(suggestion_offset); + let fold_offset = fold_point.to_offset(&snapshot.fold_snapshot); + edits.insert(fold_offset); } } @@ -455,16 +435,14 @@ impl InlayMap { let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); let fold_point = snapshot - .suggestion_snapshot .fold_snapshot .to_fold_point(buffer_point, Bias::Left); - let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point); - let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point); - edits.insert(suggestion_offset); + let fold_offset = fold_point.to_offset(&snapshot.fold_snapshot); + edits.insert(fold_offset); } - let suggestion_snapshot = snapshot.suggestion_snapshot.clone(); - let suggestion_edits = edits + let fold_snapshot = snapshot.fold_snapshot.clone(); + let fold_edits = edits .into_iter() .map(|offset| Edit { old: offset..offset, @@ -472,11 +450,11 @@ impl InlayMap { }) .collect(); drop(snapshot); - self.sync(suggestion_snapshot, suggestion_edits) + self.sync(fold_snapshot, fold_edits) } #[cfg(any(test, feature = "test-support"))] - pub fn randomly_mutate( + pub(crate) fn randomly_mutate( &mut self, next_inlay_id: &mut usize, rng: &mut rand::rngs::StdRng, @@ -522,22 +500,22 @@ impl InlayMap { impl InlaySnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.suggestion_snapshot.buffer_snapshot() + self.fold_snapshot.buffer_snapshot() } pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { let mut cursor = self .transforms - .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>(); + .cursor::<(InlayOffset, (InlayPoint, FoldOffset))>(); cursor.seek(&offset, Bias::Right, &()); let overshoot = offset.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let suggestion_offset_start = cursor.start().1 .1; - let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot); - let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start); - let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end); - InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) + let fold_offset_start = cursor.start().1 .1; + let fold_offset_end = FoldOffset(fold_offset_start.0 + overshoot); + let fold_start = fold_offset_start.to_point(&self.fold_snapshot); + let fold_end = fold_offset_end.to_point(&self.fold_snapshot); + InlayPoint(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.offset_to_point(overshoot); @@ -558,16 +536,16 @@ impl InlaySnapshot { pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { let mut cursor = self .transforms - .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>(); + .cursor::<(InlayPoint, (InlayOffset, FoldPoint))>(); cursor.seek(&point, Bias::Right, &()); let overshoot = point.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let suggestion_point_start = cursor.start().1 .1; - let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot); - let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start); - let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end); - InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0)) + let fold_point_start = cursor.start().1 .1; + let fold_point_end = FoldPoint(fold_point_start.0 + overshoot); + let fold_start = fold_point_start.to_offset(&self.fold_snapshot); + let fold_end = fold_point_end.to_offset(&self.fold_snapshot); + InlayOffset(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.point_to_offset(overshoot); @@ -582,34 +560,34 @@ impl InlaySnapshot { .flat_map(|chunk| chunk.text.chars()) } - pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + pub fn to_fold_point(&self, point: InlayPoint) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); cursor.seek(&point, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = point.0 - cursor.start().0 .0; - SuggestionPoint(cursor.start().1 .0 + overshoot) + FoldPoint(cursor.start().1 .0 + overshoot) } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.suggestion_snapshot.max_point(), + None => self.fold_snapshot.max_point(), } } - pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset { - let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); + pub fn to_fold_offset(&self, offset: InlayOffset) -> FoldOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(); cursor.seek(&offset, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = offset - cursor.start().0; - cursor.start().1 + SuggestionOffset(overshoot.0) + cursor.start().1 + FoldOffset(overshoot.0) } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.suggestion_snapshot.len(), + None => self.fold_snapshot.len(), } } - pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>(); + pub fn to_inlay_point(&self, point: FoldPoint) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&point, Bias::Left, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { @@ -622,7 +600,7 @@ impl InlaySnapshot { } pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); cursor.seek(&point, Bias::Left, &()); let mut bias = bias; @@ -644,10 +622,9 @@ impl InlaySnapshot { } else { point.0 - cursor.start().0 .0 }; - let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot); - let clipped_suggestion_point = - self.suggestion_snapshot.clip_point(suggestion_point, bias); - let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0; + let fold_point = FoldPoint(cursor.start().1 .0 + overshoot); + let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias); + let clipped_overshoot = clipped_fold_point.0 - cursor.start().1 .0; return InlayPoint(cursor.start().0 .0 + clipped_overshoot); } Some(Transform::Inlay(_)) => skipped_inlay = true, @@ -668,20 +645,19 @@ impl InlaySnapshot { pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let suggestion_start = cursor.start().1 .0; - let suffix_start = SuggestionPoint(suggestion_start + overshoot); - let suffix_end = SuggestionPoint( - suggestion_start - + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0), + let fold_start = cursor.start().1 .0; + let suffix_start = FoldPoint(fold_start + overshoot); + let suffix_end = FoldPoint( + fold_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0), ); summary = self - .suggestion_snapshot + .fold_snapshot .text_summary_for_range(suffix_start..suffix_end); cursor.next(&()); } @@ -705,9 +681,9 @@ impl InlaySnapshot { match cursor.item() { Some(Transform::Isomorphic(_)) => { let prefix_start = cursor.start().1; - let prefix_end = SuggestionPoint(prefix_start.0 + overshoot); + let prefix_end = FoldPoint(prefix_start.0 + overshoot); summary += self - .suggestion_snapshot + .fold_snapshot .text_summary_for_range(prefix_start..prefix_end); } Some(Transform::Inlay(inlay)) => { @@ -722,30 +698,27 @@ impl InlaySnapshot { } pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { - let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left, &()); - let mut suggestion_point = cursor.start().1; - let suggestion_row = if row == 0 { + let mut fold_point = cursor.start().1; + let fold_row = if row == 0 { 0 } else { match cursor.item() { Some(Transform::Isomorphic(_)) => { - suggestion_point.0 += inlay_point.0 - cursor.start().0 .0; - suggestion_point.row() + fold_point.0 += inlay_point.0 - cursor.start().0 .0; + fold_point.row() } - _ => cmp::min( - suggestion_point.row() + 1, - self.suggestion_snapshot.max_point().row(), - ), + _ => cmp::min(fold_point.row() + 1, self.fold_snapshot.max_point().row()), } }; InlayBufferRows { transforms: cursor, inlay_row: inlay_point.row(), - suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_row), + fold_rows: self.fold_snapshot.buffer_rows(fold_row), } } @@ -764,28 +737,24 @@ impl InlaySnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + inlay_highlight_style: Option, ) -> InlayChunks<'a> { - let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>(); + let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(); cursor.seek(&range.start, Bias::Right, &()); - let suggestion_range = - self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end); - let suggestion_chunks = self.suggestion_snapshot.chunks( - suggestion_range, - language_aware, - text_highlights, - suggestion_highlight, - ); + let fold_range = self.to_fold_offset(range.start)..self.to_fold_offset(range.end); + let fold_chunks = self + .fold_snapshot + .chunks(fold_range, language_aware, text_highlights); InlayChunks { transforms: cursor, - suggestion_chunks, + fold_chunks, inlay_chunks: None, - suggestion_chunk: None, + fold_chunk: None, output_offset: range.start, max_output_offset: range.end, - highlight_style: suggestion_highlight, + highlight_style: inlay_highlight_style, } } @@ -801,7 +770,7 @@ impl InlaySnapshot { { assert_eq!( self.transforms.summary().input, - self.suggestion_snapshot.text_summary() + self.fold_snapshot.text_summary() ); } } @@ -830,10 +799,7 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { #[cfg(test)] mod tests { use super::*; - use crate::{ - display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, - MultiBuffer, - }; + use crate::{display_map::fold_map::FoldMap, MultiBuffer}; use gpui::AppContext; use rand::prelude::*; use settings::SettingsStore; @@ -846,8 +812,7 @@ mod tests { let buffer = MultiBuffer::build_simple("abcdefghi", cx); let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abcdefghi"); let mut next_inlay_id = 0; @@ -863,27 +828,27 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 0)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 0)), InlayPoint::new(0, 0) ); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 1)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 1)), InlayPoint::new(0, 1) ); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 2)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 2)), InlayPoint::new(0, 2) ); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 3)), InlayPoint::new(0, 3) ); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 4)), InlayPoint::new(0, 9) ); assert_eq!( - inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 5)), + inlay_snapshot.to_inlay_point(FoldPoint::new(0, 5)), InlayPoint::new(0, 10) ); assert_eq!( @@ -919,9 +884,7 @@ mod tests { buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); // An edit surrounding the inlay should invalidate it. @@ -930,9 +893,7 @@ mod tests { buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); let (inlay_snapshot, _) = inlay_map.splice( @@ -962,9 +923,7 @@ mod tests { buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits); + let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. @@ -977,8 +936,7 @@ mod tests { fn test_buffer_rows(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); let (_, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); let (inlay_snapshot, _) = inlay_map.splice( @@ -1035,12 +993,11 @@ mod tests { log::info!("buffer text: {:?}", buffer_snapshot.text()); let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); let mut next_inlay_id = 0; for _ in 0..operations { - let mut suggestion_edits = Patch::default(); + let mut fold_edits = Patch::default(); let mut inlay_edits = Patch::default(); let mut prev_inlay_text = inlay_snapshot.text(); @@ -1052,10 +1009,8 @@ mod tests { inlay_edits = Patch::new(edits); } 30..=59 => { - for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - fold_snapshot = new_fold_snapshot; - let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_edits = suggestion_edits.compose(edits); + for (_, edits) in fold_map.randomly_mutate(&mut rng) { + fold_edits = fold_edits.compose(edits); } } _ => buffer.update(cx, |buffer, cx| { @@ -1069,21 +1024,17 @@ mod tests { }), }; - let (new_fold_snapshot, fold_edits) = + let (new_fold_snapshot, new_fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); fold_snapshot = new_fold_snapshot; - let (new_suggestion_snapshot, new_suggestion_edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits = suggestion_edits.compose(new_suggestion_edits); + fold_edits = fold_edits.compose(new_fold_edits); let (new_inlay_snapshot, new_inlay_edits) = - inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner()); + inlay_map.sync(fold_snapshot.clone(), fold_edits.into_inner()); inlay_snapshot = new_inlay_snapshot; inlay_edits = inlay_edits.compose(new_inlay_edits); log::info!("buffer text: {:?}", buffer_snapshot.text()); log::info!("folds text: {:?}", fold_snapshot.text()); - log::info!("suggestions text: {:?}", suggestion_snapshot.text()); log::info!("inlay text: {:?}", inlay_snapshot.text()); let inlays = inlay_map @@ -1093,12 +1044,11 @@ mod tests { .map(|inlay| { let buffer_point = inlay.position.to_point(&buffer_snapshot); let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point); - let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point); - (suggestion_offset, inlay.clone()) + let fold_offset = fold_point.to_offset(&fold_snapshot); + (fold_offset, inlay.clone()) }) .collect::>(); - let mut expected_text = Rope::from(suggestion_snapshot.text().as_str()); + let mut expected_text = Rope::from(fold_snapshot.text().as_str()); for (offset, inlay) in inlays.into_iter().rev() { expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); } @@ -1172,11 +1122,11 @@ mod tests { inlay_offset ); assert_eq!( - inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)), + inlay_snapshot.to_inlay_point(inlay_snapshot.to_fold_point(inlay_point)), inlay_snapshot.clip_point(inlay_point, Bias::Left), - "to_suggestion_point({:?}) = {:?}", + "to_fold_point({:?}) = {:?}", inlay_point, - inlay_snapshot.to_suggestion_point(inlay_point), + inlay_snapshot.to_fold_point(inlay_point), ); let mut bytes = [0; 4]; diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 5c2f05d67c8bc4f3b8217911b50029def354746d..0deb0f888ff6334cdef3a451ae16a5d9b4e331a4 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -316,27 +316,15 @@ impl TabSnapshot { } pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { - let fold_point = self - .inlay_snapshot - .suggestion_snapshot - .fold_snapshot - .to_fold_point(point, bias); - let suggestion_point = self - .inlay_snapshot - .suggestion_snapshot - .to_suggestion_point(fold_point); - let inlay_point = self.inlay_snapshot.to_inlay_point(suggestion_point); + let fold_point = self.inlay_snapshot.fold_snapshot.to_fold_point(point, bias); + let inlay_point = self.inlay_snapshot.to_inlay_point(fold_point); self.to_tab_point(inlay_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { let inlay_point = self.to_inlay_point(point, bias).0; - let suggestion_point = self.inlay_snapshot.to_suggestion_point(inlay_point); - let fold_point = self - .inlay_snapshot - .suggestion_snapshot - .to_fold_point(suggestion_point); - fold_point.to_buffer_point(&self.inlay_snapshot.suggestion_snapshot.fold_snapshot) + let fold_point = self.inlay_snapshot.to_fold_point(inlay_point); + fold_point.to_buffer_point(&self.inlay_snapshot.fold_snapshot) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { @@ -579,7 +567,7 @@ impl<'a> Iterator for TabChunks<'a> { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, inlay_map::InlayMap, suggestion_map::SuggestionMap}, + display_map::{fold_map::FoldMap, inlay_map::InlayMap}, MultiBuffer, }; use rand::{prelude::StdRng, Rng}; @@ -589,8 +577,7 @@ mod tests { let buffer = MultiBuffer::build_simple("", cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); @@ -607,8 +594,7 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -656,8 +642,7 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -671,8 +656,7 @@ mod tests { let buffer = MultiBuffer::build_simple(&input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot); + let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); assert_eq!( @@ -734,10 +718,8 @@ mod tests { fold_map.randomly_mutate(&mut rng); let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); - let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (_, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, _) = InlayMap::new(fold_snapshot.clone()); + let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index a18cc7da56b728c53f6e79f0dcd4797e969a571e..2e2fa449bd33d33b33851073933716789ccde148 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -763,25 +763,12 @@ impl WrapSnapshot { for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, Bias::Left).0; - let suggestion_point = self - .tab_snapshot - .inlay_snapshot - .to_suggestion_point(inlay_point); - let fold_point = self - .tab_snapshot - .inlay_snapshot - .suggestion_snapshot - .to_fold_point(suggestion_point); + let fold_point = self.tab_snapshot.inlay_snapshot.to_fold_point(inlay_point); if fold_point.row() == prev_fold_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let buffer_point = fold_point.to_buffer_point( - &self - .tab_snapshot - .inlay_snapshot - .suggestion_snapshot - .fold_snapshot, - ); + let buffer_point = + fold_point.to_buffer_point(&self.tab_snapshot.inlay_snapshot.fold_snapshot); expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); prev_fold_row = fold_point.row(); } @@ -1045,9 +1032,7 @@ fn consolidate_wrap_edits(edits: &mut Vec) { mod tests { use super::*; use crate::{ - display_map::{ - fold_map::FoldMap, inlay_map::InlayMap, suggestion_map::SuggestionMap, tab_map::TabMap, - }, + display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, MultiBuffer, }; use gpui::test::observe; @@ -1100,9 +1085,7 @@ mod tests { log::info!("Buffer text: {:?}", buffer_snapshot.text()); let (mut fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); log::info!("InlaysMap text: {:?}", inlay_snapshot.text()); let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); @@ -1133,6 +1116,7 @@ mod tests { ); log::info!("Wrapped text: {:?}", actual_text); + let mut next_inlay_id = 0; let mut edits = Vec::new(); for _i in 0..operations { log::info!("{} ==============================================", _i); @@ -1150,10 +1134,8 @@ mod tests { } 20..=39 => { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + inlay_map.sync(fold_snapshot, fold_edits); let (tabs_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (mut snapshot, wrap_edits) = @@ -1164,10 +1146,8 @@ mod tests { } } 40..=59 => { - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.randomly_mutate(&mut rng); let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); let (tabs_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); let (mut snapshot, wrap_edits) = @@ -1190,11 +1170,8 @@ mod tests { log::info!("Buffer text: {:?}", buffer_snapshot.text()); let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(suggestion_snapshot, suggestion_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (tabs_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); From e744fb88423f8d5c727fd2d7ae14c172a3024f80 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 15 Jun 2023 11:54:39 +0300 Subject: [PATCH 097/169] Avoid having carriage returns (\r) in inlays Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 615d08fa87640fc824a2149d59b58d84452ef279..c3ccaf161e046b703591594d66b1358f2b607005 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -472,6 +472,7 @@ impl InlayMap { let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = rng.gen_range(1..=5); let text = util::RandomCharIter::new(&mut *rng) + .filter(|ch| *ch != '\r') .take(len) .collect::(); log::info!( From 10765d69f48443aae4c25291566b71700cdf484e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 15 Jun 2023 16:30:32 +0300 Subject: [PATCH 098/169] Move inlay map to be the first one Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 60 +- crates/editor/src/display_map/block_map.rs | 53 +- crates/editor/src/display_map/fold_map.rs | 644 +++++++------ crates/editor/src/display_map/inlay_map.rs | 540 +++++------ .../editor/src/display_map/suggestion_map.rs | 875 ------------------ crates/editor/src/display_map/tab_map.rs | 177 ++-- crates/editor/src/display_map/wrap_map.rs | 45 +- crates/editor/src/editor.rs | 2 +- 8 files changed, 772 insertions(+), 1624 deletions(-) delete mode 100644 crates/editor/src/display_map/suggestion_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f7660ad2b31d1781763932a65cad91fb89824cbb..cddc10fba7d4c74ba8ff649abb324a8b04f80766 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -72,8 +72,8 @@ impl DisplayMap { let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let tab_size = Self::tab_size(&buffer, cx); - let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (inlay_map, snapshot) = InlayMap::new(snapshot); + let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + let (fold_map, snapshot) = FoldMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); @@ -94,10 +94,10 @@ impl DisplayMap { pub fn snapshot(&mut self, cx: &mut ModelContext) -> DisplaySnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); - let (inlay_snapshot, edits) = self.inlay_map.sync(fold_snapshot.clone(), edits); + let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (tab_snapshot, edits) = self.tab_map.sync(inlay_snapshot.clone(), edits, tab_size); + let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); @@ -132,15 +132,14 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -157,15 +156,14 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -181,8 +179,8 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -199,8 +197,8 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -253,9 +251,9 @@ impl DisplayMap { ) { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.fold_map.read(buffer_snapshot.clone(), edits); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -263,6 +261,7 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -315,9 +314,9 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Left); - *fold_point.column_mut() = 0; - point = fold_point.to_buffer_point(&self.fold_snapshot); + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + inlay_point.0.column = 0; + point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); *display_point.column_mut() = 0; @@ -331,9 +330,9 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Right); - *fold_point.column_mut() = self.fold_snapshot.line_len(fold_point.row()); - point = fold_point.to_buffer_point(&self.fold_snapshot); + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + inlay_point.0.column = self.inlay_snapshot.line_len(inlay_point.row()); + point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); *display_point.column_mut() = self.line_len(display_point.row()); @@ -363,9 +362,9 @@ impl DisplaySnapshot { } fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { - let fold_point = self.fold_snapshot.to_fold_point(point, bias); - let inlay_point = self.inlay_snapshot.to_inlay_point(fold_point); - let tab_point = self.tab_snapshot.to_tab_point(inlay_point); + let inlay_point = self.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + let tab_point = self.tab_snapshot.to_tab_point(fold_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); DisplayPoint(block_point) @@ -375,9 +374,9 @@ impl DisplaySnapshot { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); - let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, bias).0; - let fold_point = self.inlay_snapshot.to_fold_point(inlay_point); - fold_point.to_buffer_point(&self.fold_snapshot) + let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + self.inlay_snapshot.to_buffer_point(inlay_point) } pub fn max_point(&self) -> DisplayPoint { @@ -407,13 +406,13 @@ impl DisplaySnapshot { &self, display_rows: Range, language_aware: bool, - suggestion_highlight: Option, + inlay_highlights: Option, ) -> DisplayChunks<'_> { self.block_snapshot.chunks( display_rows, language_aware, Some(&self.text_highlights), - suggestion_highlight, + inlay_highlights, ) } @@ -789,9 +788,10 @@ impl DisplayPoint { pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); - let inlay_point = map.tab_snapshot.to_inlay_point(tab_point, bias).0; - let fold_point = map.inlay_snapshot.to_fold_point(inlay_point); - fold_point.to_buffer_offset(&map.fold_snapshot) + let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; + let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); + map.inlay_snapshot + .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 3bb8ccc2ac071b5b09e06ac345b23aa9466871ca..a745fddce78e82b66eff82565736cc6e68bcd080 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -583,7 +583,7 @@ impl BlockSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + inlay_highlights: Option, ) -> BlockChunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); @@ -616,7 +616,7 @@ impl BlockSnapshot { input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + inlay_highlights, ), input_chunk: Default::default(), transforms: cursor, @@ -1030,9 +1030,9 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 1.try_into().unwrap()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1175,11 +1175,11 @@ mod tests { buffer.snapshot(cx) }); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot, subscription.consume().into_inner()); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, 4.try_into().unwrap()); + tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1204,9 +1204,9 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1276,9 +1276,9 @@ mod tests { }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( @@ -1331,11 +1331,11 @@ mod tests { }) .collect::>(); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), vec![]); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), vec![]); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1355,11 +1355,11 @@ mod tests { }) .collect(); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), vec![]); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), vec![]); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1378,9 +1378,10 @@ mod tests { } } - let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); - let (tab_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 5e4eeca454814f44ff77cfd2dfdacd9c3fbd5a6b..0cd3817814ad6319e811e52b783ea82d97c520b6 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,7 +1,10 @@ -use super::TextHighlights; +use super::{ + inlay_map::{InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + TextHighlights, +}; use crate::{ - multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, - ToOffset, + multi_buffer::{MultiBufferChunks, MultiBufferRows}, + Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset, }; use collections::BTreeMap; use gpui::{color::Color, fonts::HighlightStyle}; @@ -29,6 +32,10 @@ impl FoldPoint { self.0.row } + pub fn column(self) -> u32 { + self.0.column + } + pub fn row_mut(&mut self) -> &mut u32 { &mut self.0.row } @@ -37,20 +44,20 @@ impl FoldPoint { &mut self.0.column } - pub fn to_buffer_point(self, snapshot: &FoldSnapshot) -> Point { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>(); + pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - cursor.start().1 + overshoot + InlayPoint(cursor.start().1 .0 + overshoot) } - pub fn to_buffer_offset(self, snapshot: &FoldSnapshot) -> usize { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>(); + pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; snapshot - .buffer_snapshot - .point_to_offset(cursor.start().1 + overshoot) + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + overshoot)) } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { @@ -58,17 +65,25 @@ impl FoldPoint { .transforms .cursor::<(FoldPoint, TransformSummary)>(); cursor.seek(&self, Bias::Right, &()); + let inlay_snapshot = &snapshot.inlay_snapshot; + let to_inlay_offset = |buffer_offset: usize| { + let buffer_point = inlay_snapshot.buffer.offset_to_point(buffer_offset); + inlay_snapshot.to_offset(inlay_snapshot.to_inlay_point(buffer_point)) + }; + let mut inlay_offset = to_inlay_offset(cursor.start().1.output.len); let overshoot = self.0 - cursor.start().1.output.lines; - let mut offset = cursor.start().1.output.len; if !overshoot.is_zero() { let transform = cursor.item().expect("display point out of range"); assert!(transform.output_text.is_none()); - let end_buffer_offset = snapshot - .buffer_snapshot - .point_to_offset(cursor.start().1.input.lines + overshoot); - offset += end_buffer_offset - cursor.start().1.input.len; + let end_snapshot_offset = snapshot + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); + inlay_offset += end_snapshot_offset - to_inlay_offset(cursor.start().1.input.len); } - FoldOffset(offset) + + snapshot + .to_fold_point(inlay_snapshot.to_point(inlay_offset), Bias::Right) + .to_offset(snapshot) } } @@ -87,8 +102,9 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let buffer = self.0.buffer.lock().clone(); + let snapshot = self.0.inlay_snapshot.lock().clone(); for range in ranges.into_iter() { + let buffer = &snapshot.buffer; let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer); // Ignore any empty ranges. @@ -103,31 +119,35 @@ impl<'a> FoldMapWriter<'a> { } folds.push(fold); - edits.push(text::Edit { - old: range.clone(), - new: range, + + let inlay_range = + snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } - folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, &buffer)); + let buffer = &snapshot.buffer; + folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, buffer)); self.0.folds = { let mut new_tree = SumTree::new(); let mut cursor = self.0.folds.cursor::(); for fold in folds { - new_tree.append(cursor.slice(&fold, Bias::Right, &buffer), &buffer); - new_tree.push(fold, &buffer); + new_tree.append(cursor.slice(&fold, Bias::Right, buffer), buffer); + new_tree.push(fold, buffer); } - new_tree.append(cursor.suffix(&buffer), &buffer); + new_tree.append(cursor.suffix(buffer), buffer); new_tree }; - consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(buffer.clone(), edits); + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); let snapshot = FoldSnapshot { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), - buffer_snapshot: buffer, + inlay_snapshot: snapshot, version: self.0.version.load(SeqCst), ellipses_color: self.0.ellipses_color, }; @@ -141,20 +161,23 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let buffer = self.0.buffer.lock().clone(); + let snapshot = self.0.inlay_snapshot.lock().clone(); + let buffer = &snapshot.buffer; for range in ranges.into_iter() { // Remove intersecting folds and add their ranges to edits that are passed to sync. - let mut folds_cursor = intersecting_folds(&buffer, &self.0.folds, range, inclusive); + let mut folds_cursor = intersecting_folds(&snapshot, &self.0.folds, range, inclusive); while let Some(fold) = folds_cursor.item() { - let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer); + let offset_range = fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer); if offset_range.end > offset_range.start { - edits.push(text::Edit { - old: offset_range.clone(), - new: offset_range, + let inlay_range = snapshot.to_inlay_offset(offset_range.start) + ..snapshot.to_inlay_offset(offset_range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } fold_ixs_to_delete.push(*folds_cursor.start()); - folds_cursor.next(&buffer); + folds_cursor.next(buffer); } } @@ -165,19 +188,19 @@ impl<'a> FoldMapWriter<'a> { let mut cursor = self.0.folds.cursor::(); let mut folds = SumTree::new(); for fold_ix in fold_ixs_to_delete { - folds.append(cursor.slice(&fold_ix, Bias::Right, &buffer), &buffer); - cursor.next(&buffer); + folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer); + cursor.next(buffer); } - folds.append(cursor.suffix(&buffer), &buffer); + folds.append(cursor.suffix(buffer), buffer); folds }; - consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(buffer.clone(), edits); + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); let snapshot = FoldSnapshot { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), - buffer_snapshot: buffer, + inlay_snapshot: snapshot, version: self.0.version.load(SeqCst), ellipses_color: self.0.ellipses_color, }; @@ -186,7 +209,7 @@ impl<'a> FoldMapWriter<'a> { } pub struct FoldMap { - buffer: Mutex, + inlay_snapshot: Mutex, transforms: Mutex>, folds: SumTree, version: AtomicUsize, @@ -194,15 +217,15 @@ pub struct FoldMap { } impl FoldMap { - pub fn new(buffer: MultiBufferSnapshot) -> (Self, FoldSnapshot) { + pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { let this = Self { - buffer: Mutex::new(buffer.clone()), + inlay_snapshot: Mutex::new(inlay_snapshot.clone()), folds: Default::default(), transforms: Mutex::new(SumTree::from_item( Transform { summary: TransformSummary { - input: buffer.text_summary(), - output: buffer.text_summary(), + input: inlay_snapshot.text_summary(), + output: inlay_snapshot.text_summary(), }, output_text: None, }, @@ -215,7 +238,7 @@ impl FoldMap { let snapshot = FoldSnapshot { transforms: this.transforms.lock().clone(), folds: this.folds.clone(), - buffer_snapshot: this.buffer.lock().clone(), + inlay_snapshot: inlay_snapshot.clone(), version: this.version.load(SeqCst), ellipses_color: None, }; @@ -224,15 +247,15 @@ impl FoldMap { pub fn read( &self, - buffer: MultiBufferSnapshot, - edits: Vec>, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldSnapshot, Vec) { - let edits = self.sync(buffer, edits); + let edits = self.sync(inlay_snapshot, edits); self.check_invariants(); let snapshot = FoldSnapshot { transforms: self.transforms.lock().clone(), folds: self.folds.clone(), - buffer_snapshot: self.buffer.lock().clone(), + inlay_snapshot: self.inlay_snapshot.lock().clone(), version: self.version.load(SeqCst), ellipses_color: self.ellipses_color, }; @@ -241,10 +264,10 @@ impl FoldMap { pub fn write( &mut self, - buffer: MultiBufferSnapshot, - edits: Vec>, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldMapWriter, FoldSnapshot, Vec) { - let (snapshot, edits) = self.read(buffer, edits); + let (snapshot, edits) = self.read(inlay_snapshot, edits); (FoldMapWriter(self), snapshot, edits) } @@ -259,146 +282,109 @@ impl FoldMap { fn check_invariants(&self) { if cfg!(test) { + let inlay_snapshot = self.inlay_snapshot.lock(); assert_eq!( self.transforms.lock().summary().input.len, - self.buffer.lock().len(), - "transform tree does not match buffer's length" + inlay_snapshot.to_buffer_offset(inlay_snapshot.len()), + "transform tree does not match inlay snapshot's length" ); let mut folds = self.folds.iter().peekable(); while let Some(fold) = folds.next() { if let Some(next_fold) = folds.peek() { - let comparison = fold.0.cmp(&next_fold.0, &self.buffer.lock()); + let comparison = fold.0.cmp(&next_fold.0, &self.inlay_snapshot.lock().buffer); assert!(comparison.is_le()); } } } } - fn sync( - &self, - new_buffer: MultiBufferSnapshot, - buffer_edits: Vec>, - ) -> Vec { - if buffer_edits.is_empty() { - let mut buffer = self.buffer.lock(); - if buffer.edit_count() != new_buffer.edit_count() - || buffer.parse_count() != new_buffer.parse_count() - || buffer.diagnostics_update_count() != new_buffer.diagnostics_update_count() - || buffer.git_diff_update_count() != new_buffer.git_diff_update_count() - || buffer.trailing_excerpt_update_count() - != new_buffer.trailing_excerpt_update_count() - { - self.version.fetch_add(1, SeqCst); - } - *buffer = new_buffer; - Vec::new() - } else { - let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); + fn sync(&self, inlay_snapshot: InlaySnapshot, inlay_edits: Vec) -> Vec { + let buffer = &inlay_snapshot.buffer; + let mut snapshot = self.inlay_snapshot.lock(); - let mut new_transforms = SumTree::new(); - let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); - cursor.seek(&0, Bias::Right, &()); + let mut new_snapshot = snapshot.clone(); + if new_snapshot.version != inlay_snapshot.version { + new_snapshot.version += 1; + } - while let Some(mut edit) = buffer_edits_iter.next() { - new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - edit.new.start -= edit.old.start - cursor.start(); - edit.old.start = *cursor.start(); + let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); + let mut new_transforms = SumTree::new(); + let mut transforms = self.transforms.lock(); + let mut cursor = transforms.cursor::(); + cursor.seek(&0, Bias::Right, &()); - let mut delta = edit.new.len() as isize - edit.old.len() as isize; - loop { - edit.old.end = *cursor.start(); + while let Some(mut edit) = inlay_edits_iter.next() { + new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + edit.new.start -= edit.old.start - cursor.start(); + edit.old.start = *cursor.start(); - if let Some(next_edit) = buffer_edits_iter.peek() { - if next_edit.old.start > edit.old.end { - break; - } + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); - let next_edit = buffer_edits_iter.next().unwrap(); - delta += next_edit.new.len() as isize - next_edit.old.len() as isize; + let mut delta = edit.new.len() as isize - edit.old.len() as isize; + loop { + edit.old.end = *cursor.start(); - if next_edit.old.end >= edit.old.end { - edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); - } - } else { + if let Some(next_edit) = inlay_edits_iter.peek() { + if next_edit.old.start > edit.old.end { break; } - } - - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - let anchor = new_buffer.anchor_before(edit.new.start); - let mut folds_cursor = self.folds.cursor::(); - folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &new_buffer); + let next_edit = inlay_edits_iter.next().unwrap(); + delta += next_edit.new.len() as isize - next_edit.old.len() as isize; - let mut folds = iter::from_fn({ - let buffer = &new_buffer; - move || { - let item = folds_cursor - .item() - .map(|f| f.0.start.to_offset(buffer)..f.0.end.to_offset(buffer)); - folds_cursor.next(buffer); - item + if next_edit.old.end >= edit.old.end { + edit.old.end = next_edit.old.end; + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); } - }) - .peekable(); + } else { + break; + } + } - while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { - let mut fold = folds.next().unwrap(); - let sum = new_transforms.summary(); + edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - assert!(fold.start >= sum.input.len); + let anchor = buffer.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let mut folds_cursor = self.folds.cursor::(); + folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); - while folds - .peek() - .map_or(false, |next_fold| next_fold.start <= fold.end) - { - let next_fold = folds.next().unwrap(); - if next_fold.end > fold.end { - fold.end = next_fold.end; - } - } + let mut folds = iter::from_fn({ + move || { + let item = folds_cursor.item().map(|f| { + let fold_buffer_start = f.0.start.to_offset(buffer); + let fold_buffer_end = f.0.end.to_offset(buffer); - if fold.start > sum.input.len { - let text_summary = new_buffer - .text_summary_for_range::(sum.input.len..fold.start); - new_transforms.push( - Transform { - summary: TransformSummary { - output: text_summary.clone(), - input: text_summary, - }, - output_text: None, - }, - &(), - ); - } + inlay_snapshot.to_inlay_offset(fold_buffer_start) + ..inlay_snapshot.to_inlay_offset(fold_buffer_end) + }); + folds_cursor.next(buffer); + item + } + }) + .peekable(); - if fold.end > fold.start { - let output_text = "⋯"; - new_transforms.push( - Transform { - summary: TransformSummary { - output: TextSummary::from(output_text), - input: new_buffer.text_summary_for_range(fold.start..fold.end), - }, - output_text: Some(output_text), - }, - &(), - ); + while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + let mut fold = folds.next().unwrap(); + let sum = new_transforms.summary(); + + assert!(fold.start >= sum.input.len); + + while folds + .peek() + .map_or(false, |next_fold| next_fold.start <= fold.end) + { + let next_fold = folds.next().unwrap(); + if next_fold.end > fold.end { + fold.end = next_fold.end; } } - let sum = new_transforms.summary(); - if sum.input.len < edit.new.end { - let text_summary = new_buffer - .text_summary_for_range::(sum.input.len..edit.new.end); + if fold.start > sum.input.len { + let text_summary = + buffer.text_summary_for_range::(sum.input.len..fold.start); new_transforms.push( Transform { summary: TransformSummary { @@ -410,11 +396,25 @@ impl FoldMap { &(), ); } + + if fold.end > fold.start { + let output_text = "⋯"; + new_transforms.push( + Transform { + summary: TransformSummary { + output: TextSummary::from(output_text), + input: buffer.text_summary_for_range(fold.start..fold.end), + }, + output_text: Some(output_text), + }, + &(), + ); + } } - new_transforms.append(cursor.suffix(&()), &()); - if new_transforms.is_empty() { - let text_summary = new_buffer.text_summary(); + let sum = new_transforms.summary(); + if sum.input.len < edit.new.end { + let text_summary = buffer.text_summary_for_range(sum.input.len..edit.new.end); new_transforms.push( Transform { summary: TransformSummary { @@ -426,59 +426,74 @@ impl FoldMap { &(), ); } + } - drop(cursor); + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + let text_summary = inlay_snapshot.text_summary(); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } - let mut fold_edits = Vec::with_capacity(buffer_edits.len()); - { - let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); - let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); + drop(cursor); - for mut edit in buffer_edits { - old_transforms.seek(&edit.old.start, Bias::Left, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - edit.old.start = old_transforms.start().0; - } - let old_start = - old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); + let mut fold_edits = Vec::with_capacity(inlay_edits.len()); + { + let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); + let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); - old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - old_transforms.next(&()); - edit.old.end = old_transforms.start().0; - } - let old_end = - old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); + for mut edit in inlay_edits { + old_transforms.seek(&edit.old.start, Bias::Left, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + edit.old.start = old_transforms.start().0; + } + let old_start = + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); - new_transforms.seek(&edit.new.start, Bias::Left, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - edit.new.start = new_transforms.start().0; - } - let new_start = - new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); + old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + old_transforms.next(&()); + edit.old.end = old_transforms.start().0; + } + let old_end = + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); - new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - new_transforms.next(&()); - edit.new.end = new_transforms.start().0; - } - let new_end = - new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); + new_transforms.seek(&edit.new.start, Bias::Left, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + edit.new.start = new_transforms.start().0; + } + let new_start = + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); - fold_edits.push(FoldEdit { - old: FoldOffset(old_start)..FoldOffset(old_end), - new: FoldOffset(new_start)..FoldOffset(new_end), - }); + new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + new_transforms.next(&()); + edit.new.end = new_transforms.start().0; } + let new_end = + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); - consolidate_fold_edits(&mut fold_edits); + fold_edits.push(FoldEdit { + old: FoldOffset(old_start)..FoldOffset(old_end), + new: FoldOffset(new_start)..FoldOffset(new_end), + }); } - *transforms = new_transforms; - *self.buffer.lock() = new_buffer; - self.version.fetch_add(1, SeqCst); - fold_edits + consolidate_fold_edits(&mut fold_edits); } + + *transforms = new_transforms; + *self.inlay_snapshot.lock() = inlay_snapshot; + self.version.fetch_add(1, SeqCst); + fold_edits } } @@ -486,26 +501,22 @@ impl FoldMap { pub struct FoldSnapshot { transforms: SumTree, folds: SumTree, - buffer_snapshot: MultiBufferSnapshot, + pub inlay_snapshot: InlaySnapshot, pub version: usize, pub ellipses_color: Option, } impl FoldSnapshot { - pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - &self.buffer_snapshot - } - #[cfg(test)] pub fn text(&self) -> String { - self.chunks(FoldOffset(0)..self.len(), false, None) + self.chunks(FoldOffset(0)..self.len(), false, None, None) .map(|c| c.text) .collect() } #[cfg(test)] pub fn fold_count(&self) -> usize { - self.folds.items(&self.buffer_snapshot).len() + self.folds.items(&self.inlay_snapshot.buffer).len() } pub fn text_summary(&self) -> TextSummary { @@ -529,7 +540,8 @@ impl FoldSnapshot { let buffer_start = cursor.start().1 + start_in_transform; let buffer_end = cursor.start().1 + end_in_transform; summary = self - .buffer_snapshot + .inlay_snapshot + .buffer .text_summary_for_range(buffer_start..buffer_end); } } @@ -547,7 +559,8 @@ impl FoldSnapshot { let buffer_start = cursor.start().1; let buffer_end = cursor.start().1 + end_in_transform; summary += self - .buffer_snapshot + .inlay_snapshot + .buffer .text_summary_for_range::(buffer_start..buffer_end); } } @@ -556,8 +569,8 @@ impl FoldSnapshot { summary } - pub fn to_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(Point, FoldPoint)>(); + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); cursor.seek(&point, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { if bias == Bias::Left || point == cursor.start().0 { @@ -566,7 +579,7 @@ impl FoldSnapshot { cursor.end(&()).1 } } else { - let overshoot = point - cursor.start().0; + let overshoot = InlayPoint(point.0 - cursor.start().0 .0); FoldPoint(cmp::min( cursor.start().1 .0 + overshoot, cursor.end(&()).1 .0, @@ -599,7 +612,7 @@ impl FoldSnapshot { let overshoot = fold_point.0 - cursor.start().0 .0; let buffer_point = cursor.start().1 + overshoot; - let input_buffer_rows = self.buffer_snapshot.buffer_rows(buffer_point.row); + let input_buffer_rows = self.inlay_snapshot.buffer.buffer_rows(buffer_point.row); FoldBufferRows { fold_point, @@ -621,10 +634,10 @@ impl FoldSnapshot { where T: ToOffset, { - let mut folds = intersecting_folds(&self.buffer_snapshot, &self.folds, range, false); + let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false); iter::from_fn(move || { let item = folds.item().map(|f| &f.0); - folds.next(&self.buffer_snapshot); + folds.next(&self.inlay_snapshot.buffer); item }) } @@ -633,7 +646,7 @@ impl FoldSnapshot { where T: ToOffset, { - let offset = offset.to_offset(&self.buffer_snapshot); + let offset = offset.to_offset(&self.inlay_snapshot.buffer); let mut cursor = self.transforms.cursor::(); cursor.seek(&offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.output_text.is_some()) @@ -641,6 +654,7 @@ impl FoldSnapshot { pub fn is_line_folded(&self, buffer_row: u32) -> bool { let mut cursor = self.transforms.cursor::(); + // TODO kb is this right? cursor.seek(&Point::new(buffer_row, 0), Bias::Right, &()); while let Some(transform) = cursor.item() { if transform.output_text.is_some() { @@ -660,6 +674,7 @@ impl FoldSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, + inlay_highlights: Option, ) -> FoldChunks<'a> { let mut highlight_endpoints = Vec::new(); let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); @@ -681,12 +696,13 @@ impl FoldSnapshot { while transform_cursor.start().0 < range.end { if !transform_cursor.item().unwrap().is_fold() { let transform_start = self - .buffer_snapshot + .inlay_snapshot + .buffer .anchor_after(cmp::max(buffer_start, transform_cursor.start().1)); let transform_end = { let overshoot = range.end.0 - transform_cursor.start().0 .0; - self.buffer_snapshot.anchor_before(cmp::min( + self.inlay_snapshot.buffer.anchor_before(cmp::min( transform_cursor.end(&()).1, transform_cursor.start().1 + overshoot, )) @@ -697,7 +713,8 @@ impl FoldSnapshot { let ranges = &highlights.1; let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&transform_start, self.buffer_snapshot()); + let cmp = + probe.end.cmp(&transform_start, &self.inlay_snapshot.buffer); if cmp.is_gt() { Ordering::Greater } else { @@ -709,20 +726,20 @@ impl FoldSnapshot { for range in &ranges[start_ix..] { if range .start - .cmp(&transform_end, &self.buffer_snapshot) + .cmp(&transform_end, &self.inlay_snapshot.buffer) .is_ge() { break; } highlight_endpoints.push(HighlightEndpoint { - offset: range.start.to_offset(&self.buffer_snapshot), + offset: range.start.to_offset(&self.inlay_snapshot.buffer), is_start: true, tag: *tag, style, }); highlight_endpoints.push(HighlightEndpoint { - offset: range.end.to_offset(&self.buffer_snapshot), + offset: range.end.to_offset(&self.inlay_snapshot.buffer), is_start: false, tag: *tag, style, @@ -741,9 +758,10 @@ impl FoldSnapshot { FoldChunks { transform_cursor, buffer_chunks: self - .buffer_snapshot + .inlay_snapshot + .buffer .chunks(buffer_start..buffer_end, language_aware), - buffer_chunk: None, + inlay_chunk: None, buffer_offset: buffer_start, output_offset: range.start.0, max_output_offset: range.end.0, @@ -753,6 +771,11 @@ impl FoldSnapshot { } } + pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { + self.chunks(start.to_offset(self)..self.len(), false, None, None) + .flat_map(|chunk| chunk.text.chars()) + } + #[cfg(test)] pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset { let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>(); @@ -768,7 +791,8 @@ impl FoldSnapshot { } else { let overshoot = offset.0 - transform_start; let buffer_offset = cursor.start().1 + overshoot; - let clipped_buffer_offset = self.buffer_snapshot.clip_offset(buffer_offset, bias); + let clipped_buffer_offset = + self.inlay_snapshot.buffer.clip_offset(buffer_offset, bias); FoldOffset( (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) as usize, @@ -794,7 +818,7 @@ impl FoldSnapshot { let overshoot = point.0 - transform_start; let buffer_position = cursor.start().1 + overshoot; let clipped_buffer_position = - self.buffer_snapshot.clip_point(buffer_position, bias); + self.inlay_snapshot.buffer.clip_point(buffer_position, bias); FoldPoint(cursor.start().0 .0 + (clipped_buffer_position - cursor.start().1)) } } else { @@ -804,7 +828,7 @@ impl FoldSnapshot { } fn intersecting_folds<'a, T>( - buffer: &'a MultiBufferSnapshot, + inlay_snapshot: &'a InlaySnapshot, folds: &'a SumTree, range: Range, inclusive: bool, @@ -812,6 +836,7 @@ fn intersecting_folds<'a, T>( where T: ToOffset, { + let buffer = &inlay_snapshot.buffer; let start = buffer.anchor_before(range.start.to_offset(buffer)); let end = buffer.anchor_after(range.end.to_offset(buffer)); let mut cursor = folds.filter::<_, usize>(move |summary| { @@ -828,7 +853,7 @@ where cursor } -fn consolidate_buffer_edits(edits: &mut Vec>) { +fn consolidate_inlay_edits(edits: &mut Vec) { edits.sort_unstable_by(|a, b| { a.old .start @@ -956,7 +981,7 @@ impl Default for FoldSummary { impl sum_tree::Summary for FoldSummary { type Context = MultiBufferSnapshot; - fn add_summary(&mut self, other: &Self, buffer: &MultiBufferSnapshot) { + fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less { self.min_start = other.min_start.clone(); } @@ -1034,7 +1059,7 @@ impl<'a> Iterator for FoldBufferRows<'a> { pub struct FoldChunks<'a> { transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, buffer_chunks: MultiBufferChunks<'a>, - buffer_chunk: Option<(usize, Chunk<'a>)>, + inlay_chunk: Option<(usize, Chunk<'a>)>, buffer_offset: usize, output_offset: usize, max_output_offset: usize, @@ -1056,7 +1081,7 @@ impl<'a> Iterator for FoldChunks<'a> { // If we're in a fold, then return the fold's display text and // advance the transform and buffer cursors to the end of the fold. if let Some(output_text) = transform.output_text { - self.buffer_chunk.take(); + self.inlay_chunk.take(); self.buffer_offset += transform.summary.input.len; self.buffer_chunks.seek(self.buffer_offset); @@ -1093,13 +1118,13 @@ impl<'a> Iterator for FoldChunks<'a> { } // Retrieve a chunk from the current location in the buffer. - if self.buffer_chunk.is_none() { + if self.inlay_chunk.is_none() { let chunk_offset = self.buffer_chunks.offset(); - self.buffer_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk)); + self.inlay_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk)); } // Otherwise, take a chunk from the buffer's text. - if let Some((buffer_chunk_start, mut chunk)) = self.buffer_chunk { + if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk { let buffer_chunk_end = buffer_chunk_start + chunk.text.len(); let transform_end = self.transform_cursor.end(&()).1; let chunk_end = buffer_chunk_end @@ -1120,7 +1145,7 @@ impl<'a> Iterator for FoldChunks<'a> { if chunk_end == transform_end { self.transform_cursor.next(&()); } else if chunk_end == buffer_chunk_end { - self.buffer_chunk.take(); + self.inlay_chunk.take(); } self.buffer_offset = chunk_end; @@ -1163,11 +1188,15 @@ impl FoldOffset { .transforms .cursor::<(FoldOffset, TransformSummary)>(); cursor.seek(&self, Bias::Right, &()); + // TODO kb seems wrong to use buffer points? let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) { Point::new(0, (self.0 - cursor.start().0 .0) as u32) } else { let buffer_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; - let buffer_point = snapshot.buffer_snapshot.offset_to_point(buffer_offset); + let buffer_point = snapshot + .inlay_snapshot + .buffer + .offset_to_point(buffer_offset); buffer_point - cursor.start().1.input.lines }; FoldPoint(cursor.start().1.output.lines + overshoot) @@ -1202,6 +1231,18 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { } } +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.input.len; + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { *self += &summary.input.lines; @@ -1219,7 +1260,7 @@ pub type FoldEdit = Edit; #[cfg(test)] mod tests { use super::*; - use crate::{MultiBuffer, ToPoint}; + use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint}; use collections::HashSet; use rand::prelude::*; use settings::SettingsStore; @@ -1235,9 +1276,10 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot, vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot, vec![]); let (snapshot2, edits) = writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(2, 4)..Point::new(4, 1), @@ -1268,7 +1310,10 @@ mod tests { ); buffer.snapshot(cx) }); - let (snapshot3, edits) = map.read(buffer_snapshot, subscription.consume().into_inner()); + + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); assert_eq!( edits, @@ -1288,17 +1333,19 @@ mod tests { buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx); buffer.snapshot(cx) }); - let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits); assert_eq!(snapshot4.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false); - let (snapshot5, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot5.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true); - let (snapshot6, _) = map.read(buffer_snapshot, vec![]); + let (snapshot6, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } @@ -1308,35 +1355,36 @@ mod tests { let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); { - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![5..8]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "abcde⋯ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![0..1, 2..5]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![11..11, 8..10]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯kl"); } { - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![0..2, 2..5]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "⋯fghijkl"); // Edit within one of the folds. @@ -1344,7 +1392,9 @@ mod tests { buffer.edit([(0..1, "12345")], None, cx); buffer.snapshot(cx) }); - let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "12345⋯fghijkl"); } } @@ -1353,15 +1403,16 @@ mod tests { fn test_overlapping_folds(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(0, 4)..Point::new(1, 0), Point::new(1, 2)..Point::new(3, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1371,21 +1422,24 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx); buffer.snapshot(cx) }); - let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1393,16 +1447,17 @@ mod tests { fn test_folds_in_range(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(0, 4)..Point::new(1, 0), Point::new(1, 2)..Point::new(3, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let fold_ranges = snapshot .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) .map(|fold| fold.start.to_point(&buffer_snapshot)..fold.end.to_point(&buffer_snapshot)) @@ -1431,9 +1486,10 @@ mod tests { MultiBuffer::build_random(&mut rng, cx) }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut initial_snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); let mut highlights = TreeMap::default(); @@ -1473,7 +1529,8 @@ mod tests { }), }; - let (snapshot, edits) = map.read(buffer_snapshot.clone(), buffer_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, buffer_edits); + let (snapshot, edits) = map.read(inlay_snapshot, inlay_edits); snapshot_edits.push((snapshot.clone(), edits)); let mut expected_text: String = buffer_snapshot.text().to_string(); @@ -1526,19 +1583,20 @@ mod tests { let mut fold_offset = FoldOffset(0); let mut char_column = 0; for c in expected_text.chars() { - let buffer_point = fold_point.to_buffer_point(&snapshot); - let buffer_offset = buffer_point.to_offset(&buffer_snapshot); + let inlay_point = fold_point.to_inlay_point(&snapshot); + let buffer_point = inlay_snapshot.to_buffer_point(inlay_point); + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); assert_eq!( - snapshot.to_fold_point(buffer_point, Right), + snapshot.to_fold_point(inlay_point, Right), fold_point, "{:?} -> fold point", buffer_point, ); assert_eq!( - fold_point.to_buffer_offset(&snapshot), + inlay_snapshot.to_buffer_offset(inlay_snapshot.to_offset(inlay_point)), buffer_offset, - "fold_point.to_buffer_offset({:?})", - fold_point, + "inlay_snapshot.to_buffer_offset(inlay_snapshot.to_offset(({:?}))", + inlay_point, ); assert_eq!( fold_point.to_offset(&snapshot), @@ -1579,7 +1637,7 @@ mod tests { let text = &expected_text[start.0..end.0]; assert_eq!( snapshot - .chunks(start..end, false, Some(&highlights)) + .chunks(start..end, false, Some(&highlights), None) .map(|c| c.text) .collect::(), text, @@ -1677,15 +1735,16 @@ mod tests { let buffer = MultiBuffer::build_simple(&text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( snapshot.buffer_rows(0).collect::>(), @@ -1700,13 +1759,14 @@ mod tests { impl FoldMap { fn merged_fold_ranges(&self) -> Vec> { - let buffer = self.buffer.lock().clone(); - let mut folds = self.folds.items(&buffer); + let inlay_snapshot = self.inlay_snapshot.lock().clone(); + let buffer = &inlay_snapshot.buffer; + let mut folds = self.folds.items(buffer); // Ensure sorting doesn't change how folds get merged and displayed. - folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer)); + folds.sort_by(|a, b| a.0.cmp(&b.0, buffer)); let mut fold_ranges = folds .iter() - .map(|fold| fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer)) + .map(|fold| fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer)) .peekable(); let mut merged_ranges = Vec::new(); @@ -1735,7 +1795,8 @@ mod tests { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 if !self.folds.is_empty() => { - let buffer = self.buffer.lock().clone(); + let inlay_snapshot = self.inlay_snapshot.lock().clone(); + let buffer = &inlay_snapshot.buffer; let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1743,13 +1804,14 @@ mod tests { to_unfold.push(start..end); } log::info!("unfolding {:?}", to_unfold); - let (mut writer, snapshot, edits) = self.write(buffer, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_unfold); snapshot_edits.push((snapshot, edits)); } _ => { - let buffer = self.buffer.lock().clone(); + let inlay_snapshot = self.inlay_snapshot.lock().clone(); + let buffer = &inlay_snapshot.buffer; let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1757,7 +1819,7 @@ mod tests { to_fold.push(start..end); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(buffer, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_fold); snapshot_edits.push((snapshot, edits)); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index c3ccaf161e046b703591594d66b1358f2b607005..b9e7119fc99679aaf43c7bb66aef89d7ccb0e006 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,9 +1,6 @@ -use super::{ - fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, - TextHighlights, -}; use crate::{ inlay_cache::{Inlay, InlayId, InlayProperties}, + multi_buffer::{MultiBufferChunks, MultiBufferRows}, MultiBufferSnapshot, ToPoint, }; use collections::{BTreeSet, HashMap}; @@ -16,17 +13,19 @@ use std::{ }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; +use util::post_inc; pub struct InlayMap { - snapshot: Mutex, + buffer: Mutex, + transforms: SumTree, inlays_by_id: HashMap, inlays: Vec, + version: usize, } #[derive(Clone)] pub struct InlaySnapshot { - // TODO kb merge these two together - pub fold_snapshot: FoldSnapshot, + pub buffer: MultiBufferSnapshot, transforms: SumTree, pub version: usize, } @@ -102,12 +101,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += &summary.input.len; - } -} - #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct InlayPoint(pub Point); @@ -117,23 +110,29 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += &summary.input.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += &summary.input.lines; + *self += &summary.input.lines; } } #[derive(Clone)] pub struct InlayBufferRows<'a> { - transforms: Cursor<'a, Transform, (InlayPoint, FoldPoint)>, - fold_rows: FoldBufferRows<'a>, + transforms: Cursor<'a, Transform, (InlayPoint, Point)>, + buffer_rows: MultiBufferRows<'a>, inlay_row: u32, } pub struct InlayChunks<'a> { - transforms: Cursor<'a, Transform, (InlayOffset, FoldOffset)>, - fold_chunks: FoldChunks<'a>, - fold_chunk: Option>, + transforms: Cursor<'a, Transform, (InlayOffset, usize)>, + buffer_chunks: MultiBufferChunks<'a>, + buffer_chunk: Option>, inlay_chunks: Option>, output_offset: InlayOffset, max_output_offset: InlayOffset, @@ -151,10 +150,10 @@ impl<'a> Iterator for InlayChunks<'a> { let chunk = match self.transforms.item()? { Transform::Isomorphic(_) => { let chunk = self - .fold_chunk - .get_or_insert_with(|| self.fold_chunks.next().unwrap()); + .buffer_chunk + .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); if chunk.text.is_empty() { - *chunk = self.fold_chunks.next().unwrap(); + *chunk = self.buffer_chunks.next().unwrap(); } let (prefix, suffix) = chunk.text.split_at(cmp::min( @@ -201,11 +200,11 @@ impl<'a> Iterator for InlayBufferRows<'a> { fn next(&mut self) -> Option { let buffer_row = if self.inlay_row == 0 { - self.fold_rows.next().unwrap() + self.buffer_rows.next().unwrap() } else { match self.transforms.item()? { Transform::Inlay(_) => None, - Transform::Isomorphic(_) => self.fold_rows.next().unwrap(), + Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(), } }; @@ -232,21 +231,21 @@ impl InlayPoint { } impl InlayMap { - pub fn new(fold_snapshot: FoldSnapshot) -> (Self, InlaySnapshot) { + pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) { + let version = 0; let snapshot = InlaySnapshot { - fold_snapshot: fold_snapshot.clone(), - version: 0, - transforms: SumTree::from_item( - Transform::Isomorphic(fold_snapshot.text_summary()), - &(), - ), + buffer: buffer.clone(), + transforms: SumTree::from_item(Transform::Isomorphic(buffer.text_summary()), &()), + version, }; ( Self { - snapshot: Mutex::new(snapshot.clone()), + buffer: Mutex::new(buffer), + transforms: snapshot.transforms.clone(), inlays_by_id: Default::default(), inlays: Default::default(), + version, }, snapshot, ) @@ -254,144 +253,140 @@ impl InlayMap { pub fn sync( &mut self, - fold_snapshot: FoldSnapshot, - mut fold_edits: Vec, + buffer_snapshot: MultiBufferSnapshot, + buffer_edits: Vec>, ) -> (InlaySnapshot, Vec) { - let mut snapshot = self.snapshot.lock(); + let mut buffer = self.buffer.lock(); + if buffer_edits.is_empty() { + let new_version = if buffer.edit_count() != buffer_snapshot.edit_count() + || buffer.parse_count() != buffer_snapshot.parse_count() + || buffer.diagnostics_update_count() != buffer_snapshot.diagnostics_update_count() + || buffer.git_diff_update_count() != buffer_snapshot.git_diff_update_count() + || buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + post_inc(&mut self.version) + } else { + self.version + }; - let mut new_snapshot = snapshot.clone(); - if new_snapshot.fold_snapshot.version != fold_snapshot.version { - new_snapshot.version += 1; - } + *buffer = buffer_snapshot.clone(); + ( + InlaySnapshot { + buffer: buffer_snapshot, + transforms: SumTree::default(), + version: new_version, + }, + Vec::new(), + ) + } else { + let mut inlay_edits = Patch::default(); + let mut new_transforms = SumTree::new(); + // TODO kb something is wrong with how we store it? + let mut transforms = self.transforms; + let mut cursor = transforms.cursor::<(usize, InlayOffset)>(); + let mut buffer_edits_iter = buffer_edits.iter().peekable(); + while let Some(buffer_edit) = buffer_edits_iter.next() { + new_transforms + .push_tree(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if cursor.end(&()).0 == buffer_edit.old.start { + new_transforms.push(Transform::Isomorphic(transform.clone()), &()); + cursor.next(&()); + } + } - if fold_snapshot - .buffer_snapshot() - .trailing_excerpt_update_count() - != snapshot - .fold_snapshot - .buffer_snapshot() - .trailing_excerpt_update_count() - { - if fold_edits.is_empty() { - fold_edits.push(Edit { - old: snapshot.fold_snapshot.len()..snapshot.fold_snapshot.len(), - new: fold_snapshot.len()..fold_snapshot.len(), - }); - } - } + // Remove all the inlays and transforms contained by the edit. + let old_start = + cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0); + cursor.seek(&buffer_edit.old.end, Bias::Right, &()); + let old_end = + cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0); - let mut inlay_edits = Patch::default(); - let mut new_transforms = SumTree::new(); - let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); - let mut fold_edits_iter = fold_edits.iter().peekable(); - while let Some(fold_edit) = fold_edits_iter.next() { - new_transforms.push_tree(cursor.slice(&fold_edit.old.start, Bias::Left, &()), &()); - if let Some(Transform::Isomorphic(transform)) = cursor.item() { - if cursor.end(&()).0 == fold_edit.old.start { - new_transforms.push(Transform::Isomorphic(transform.clone()), &()); - cursor.next(&()); - } - } + // Push the unchanged prefix. + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_edit.new.start; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + let new_start = InlayOffset(new_transforms.summary().output.len); + + let start_point = buffer_edit.new.start.to_point(&buffer_snapshot); + let start_ix = match self.inlays.binary_search_by(|probe| { + probe + .position + .to_point(&buffer_snapshot) + .cmp(&start_point) + .then(std::cmp::Ordering::Greater) + }) { + Ok(ix) | Err(ix) => ix, + }; + + for inlay in &self.inlays[start_ix..] { + let buffer_point = inlay.position.to_point(&buffer_snapshot); + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + if buffer_offset > buffer_edit.new.end { + break; + } - // Remove all the inlays and transforms contained by the edit. - let old_start = - cursor.start().1 + InlayOffset(fold_edit.old.start.0 - cursor.start().0 .0); - cursor.seek(&fold_edit.old.end, Bias::Right, &()); - let old_end = cursor.start().1 + InlayOffset(fold_edit.old.end.0 - cursor.start().0 .0); - - // Push the unchanged prefix. - let prefix_start = FoldOffset(new_transforms.summary().input.len); - let prefix_end = fold_edit.new.start; - push_isomorphic( - &mut new_transforms, - fold_snapshot.text_summary_for_range( - prefix_start.to_point(&fold_snapshot)..prefix_end.to_point(&fold_snapshot), - ), - ); - let new_start = InlayOffset(new_transforms.summary().output.len); - - let start_point = fold_edit - .new - .start - .to_point(&fold_snapshot) - .to_buffer_point(&fold_snapshot); - let start_ix = match self.inlays.binary_search_by(|probe| { - probe - .position - .to_point(&fold_snapshot.buffer_snapshot()) - .cmp(&start_point) - .then(std::cmp::Ordering::Greater) - }) { - Ok(ix) | Err(ix) => ix, - }; + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_offset; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); - for inlay in &self.inlays[start_ix..] { - let buffer_point = inlay.position.to_point(fold_snapshot.buffer_snapshot()); - let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&fold_snapshot); - if fold_offset > fold_edit.new.end { - break; + if inlay.position.is_valid(&buffer_snapshot) { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + } } - let prefix_start = FoldOffset(new_transforms.summary().input.len); - let prefix_end = fold_offset; + // Apply the rest of the edit. + let transform_start = new_transforms.summary().input.len; push_isomorphic( &mut new_transforms, - fold_snapshot.text_summary_for_range( - prefix_start.to_point(&fold_snapshot)..prefix_end.to_point(&fold_snapshot), - ), + buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end), ); + let new_end = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); - if inlay.position.is_valid(fold_snapshot.buffer_snapshot()) { - new_transforms.push(Transform::Inlay(inlay.clone()), &()); + // If the next edit doesn't intersect the current isomorphic transform, then + // we can push its remainder. + if buffer_edits_iter + .peek() + .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) + { + let transform_start = new_transforms.summary().input.len; + let transform_end = + buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end); + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(transform_start..transform_end), + ); + cursor.next(&()); } } - // Apply the rest of the edit. - let transform_start = FoldOffset(new_transforms.summary().input.len); - push_isomorphic( - &mut new_transforms, - fold_snapshot.text_summary_for_range( - transform_start.to_point(&fold_snapshot) - ..fold_edit.new.end.to_point(&fold_snapshot), - ), - ); - let new_end = InlayOffset(new_transforms.summary().output.len); - inlay_edits.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - - // If the next edit doesn't intersect the current isomorphic transform, then - // we can push its remainder. - if fold_edits_iter - .peek() - .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) - { - let transform_start = FoldOffset(new_transforms.summary().input.len); - let transform_end = fold_edit.new.end + (cursor.end(&()).0 - fold_edit.old.end); - push_isomorphic( - &mut new_transforms, - fold_snapshot.text_summary_for_range( - transform_start.to_point(&fold_snapshot) - ..transform_end.to_point(&fold_snapshot), - ), - ); - cursor.next(&()); + new_transforms.push_tree(cursor.suffix(&()), &()); + if new_transforms.first().is_none() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); } - } - new_transforms.push_tree(cursor.suffix(&()), &()); - if new_transforms.first().is_none() { - new_transforms.push(Transform::Isomorphic(Default::default()), &()); - } - new_snapshot.transforms = new_transforms; - new_snapshot.fold_snapshot = fold_snapshot; - new_snapshot.check_invariants(); - drop(cursor); + let new_snapshot = InlaySnapshot { + buffer: buffer_snapshot, + transforms: new_transforms, + version: post_inc(&mut self.version), + }; + new_snapshot.check_invariants(); + drop(cursor); - *snapshot = new_snapshot.clone(); - (new_snapshot, inlay_edits.into_inner()) + *buffer = buffer_snapshot.clone(); + (new_snapshot, inlay_edits.into_inner()) + } } pub fn splice>( @@ -399,20 +394,15 @@ impl InlayMap { to_remove: Vec, to_insert: Vec<(InlayId, InlayProperties)>, ) -> (InlaySnapshot, Vec) { - let mut snapshot = self.snapshot.lock(); - snapshot.version += 1; - + let mut buffer_snapshot = self.buffer.lock(); let mut edits = BTreeSet::new(); self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); for inlay_id in to_remove { if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { - let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); - let fold_point = snapshot - .fold_snapshot - .to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&snapshot.fold_snapshot); - edits.insert(fold_offset); + let buffer_point = inlay.position.to_point(&buffer_snapshot); + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + edits.insert(buffer_offset); } } @@ -423,34 +413,28 @@ impl InlayMap { text: properties.text.into(), }; self.inlays_by_id.insert(inlay.id, inlay.clone()); - match self.inlays.binary_search_by(|probe| { - probe - .position - .cmp(&inlay.position, snapshot.buffer_snapshot()) - }) { + match self + .inlays + .binary_search_by(|probe| probe.position.cmp(&inlay.position, &buffer_snapshot)) + { Ok(ix) | Err(ix) => { self.inlays.insert(ix, inlay.clone()); } } - let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot()); - let fold_point = snapshot - .fold_snapshot - .to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&snapshot.fold_snapshot); - edits.insert(fold_offset); + let buffer_point = inlay.position.to_point(&buffer_snapshot); + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + edits.insert(buffer_offset); } - let fold_snapshot = snapshot.fold_snapshot.clone(); - let fold_edits = edits + let buffer_edits = edits .into_iter() .map(|offset| Edit { old: offset..offset, new: offset..offset, }) .collect(); - drop(snapshot); - self.sync(fold_snapshot, fold_edits) + self.sync(buffer_snapshot.clone(), buffer_edits) } #[cfg(any(test, feature = "test-support"))] @@ -460,14 +444,12 @@ impl InlayMap { rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec) { use rand::prelude::*; - use util::post_inc; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let snapshot = self.snapshot.lock(); + let buffer_snapshot = self.buffer.lock(); for _ in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { - let buffer_snapshot = snapshot.buffer_snapshot(); let position = buffer_snapshot.random_byte_range(0, rng).start; let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = rng.gen_range(1..=5); @@ -494,29 +476,25 @@ impl InlayMap { } log::info!("removing inlays: {:?}", to_remove); - drop(snapshot); + drop(buffer_snapshot); self.splice(to_remove, to_insert) } } impl InlaySnapshot { - pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.fold_snapshot.buffer_snapshot() - } - pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { let mut cursor = self .transforms - .cursor::<(InlayOffset, (InlayPoint, FoldOffset))>(); + .cursor::<(InlayOffset, (InlayPoint, usize))>(); cursor.seek(&offset, Bias::Right, &()); let overshoot = offset.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let fold_offset_start = cursor.start().1 .1; - let fold_offset_end = FoldOffset(fold_offset_start.0 + overshoot); - let fold_start = fold_offset_start.to_point(&self.fold_snapshot); - let fold_end = fold_offset_end.to_point(&self.fold_snapshot); - InlayPoint(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0)) + let buffer_offset_start = cursor.start().1 .1; + let buffer_offset_end = buffer_offset_start + overshoot; + let buffer_start = self.buffer.offset_to_point(buffer_offset_start); + let buffer_end = self.buffer.offset_to_point(buffer_offset_end); + InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.offset_to_point(overshoot); @@ -537,16 +515,16 @@ impl InlaySnapshot { pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { let mut cursor = self .transforms - .cursor::<(InlayPoint, (InlayOffset, FoldPoint))>(); + .cursor::<(InlayPoint, (InlayOffset, Point))>(); cursor.seek(&point, Bias::Right, &()); let overshoot = point.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let fold_point_start = cursor.start().1 .1; - let fold_point_end = FoldPoint(fold_point_start.0 + overshoot); - let fold_start = fold_point_start.to_offset(&self.fold_snapshot); - let fold_end = fold_point_end.to_offset(&self.fold_snapshot); - InlayOffset(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0)) + let buffer_point_start = cursor.start().1 .1; + let buffer_point_end = buffer_point_start + overshoot; + let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start); + let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end); + InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.point_to_offset(overshoot); @@ -557,42 +535,55 @@ impl InlaySnapshot { } pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator { - self.chunks(self.to_offset(start)..self.len(), false, None, None) + self.chunks(self.to_offset(start)..self.len(), false, None) .flat_map(|chunk| chunk.text.chars()) } - pub fn to_fold_point(&self, point: InlayPoint) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); + pub fn to_buffer_point(&self, point: InlayPoint) -> Point { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); cursor.seek(&point, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = point.0 - cursor.start().0 .0; - FoldPoint(cursor.start().1 .0 + overshoot) + cursor.start().1 + overshoot } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.fold_snapshot.max_point(), + None => self.buffer.max_point(), } } - pub fn to_fold_offset(&self, offset: InlayOffset) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(); + pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); cursor.seek(&offset, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = offset - cursor.start().0; - cursor.start().1 + FoldOffset(overshoot.0) + cursor.start().1 + overshoot.0 + } + Some(Transform::Inlay(_)) => cursor.start().1, + None => self.buffer.len(), + } + } + + pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(Point, InlayOffset)>(); + cursor.seek(&offset, Bias::Left, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let overshoot = offset - cursor.start().0; + InlayOffset(cursor.start().1 .0 + overshoot) } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.fold_snapshot.len(), + None => self.len(), } } - pub fn to_inlay_point(&self, point: FoldPoint) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); + pub fn to_inlay_point(&self, point: Point) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(); cursor.seek(&point, Bias::Left, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { - let overshoot = point.0 - cursor.start().0 .0; + let overshoot = point - cursor.start().0; InlayPoint(cursor.start().1 .0 + overshoot) } Some(Transform::Inlay(_)) => cursor.start().1, @@ -601,7 +592,7 @@ impl InlaySnapshot { } pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); cursor.seek(&point, Bias::Left, &()); let mut bias = bias; @@ -623,9 +614,9 @@ impl InlaySnapshot { } else { point.0 - cursor.start().0 .0 }; - let fold_point = FoldPoint(cursor.start().1 .0 + overshoot); - let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias); - let clipped_overshoot = clipped_fold_point.0 - cursor.start().1 .0; + let buffer_point = cursor.start().1 + overshoot; + let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); + let clipped_overshoot = clipped_buffer_point - cursor.start().1; return InlayPoint(cursor.start().0 .0 + clipped_overshoot); } Some(Transform::Inlay(_)) => skipped_inlay = true, @@ -643,23 +634,24 @@ impl InlaySnapshot { } } + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output + } + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { - let fold_start = cursor.start().1 .0; - let suffix_start = FoldPoint(fold_start + overshoot); - let suffix_end = FoldPoint( - fold_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0), - ); - summary = self - .fold_snapshot - .text_summary_for_range(suffix_start..suffix_end); + let buffer_start = cursor.start().1; + let suffix_start = buffer_start + overshoot; + let suffix_end = + buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0); + summary = self.buffer.text_summary_for_range(suffix_start..suffix_end); cursor.next(&()); } Some(Transform::Inlay(inlay)) => { @@ -682,10 +674,10 @@ impl InlaySnapshot { match cursor.item() { Some(Transform::Isomorphic(_)) => { let prefix_start = cursor.start().1; - let prefix_end = FoldPoint(prefix_start.0 + overshoot); + let prefix_end = prefix_start + overshoot; summary += self - .fold_snapshot - .text_summary_for_range(prefix_start..prefix_end); + .buffer + .text_summary_for_range::(prefix_start..prefix_end); } Some(Transform::Inlay(inlay)) => { let prefix_end = inlay.text.point_to_offset(overshoot); @@ -699,27 +691,27 @@ impl InlaySnapshot { } pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { - let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left, &()); - let mut fold_point = cursor.start().1; - let fold_row = if row == 0 { + let mut buffer_point = cursor.start().1; + let buffer_row = if row == 0 { 0 } else { match cursor.item() { Some(Transform::Isomorphic(_)) => { - fold_point.0 += inlay_point.0 - cursor.start().0 .0; - fold_point.row() + buffer_point += inlay_point.0 - cursor.start().0 .0; + buffer_point.row } - _ => cmp::min(fold_point.row() + 1, self.fold_snapshot.max_point().row()), + _ => cmp::min(buffer_point.row + 1, self.buffer.max_point().row), } }; InlayBufferRows { transforms: cursor, inlay_row: inlay_point.row(), - fold_rows: self.fold_snapshot.buffer_rows(fold_row), + buffer_rows: self.buffer.buffer_rows(buffer_row), } } @@ -737,22 +729,19 @@ impl InlaySnapshot { &'a self, range: Range, language_aware: bool, - text_highlights: Option<&'a TextHighlights>, inlay_highlight_style: Option, ) -> InlayChunks<'a> { - let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(); + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); cursor.seek(&range.start, Bias::Right, &()); - let fold_range = self.to_fold_offset(range.start)..self.to_fold_offset(range.end); - let fold_chunks = self - .fold_snapshot - .chunks(fold_range, language_aware, text_highlights); + let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); + let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); InlayChunks { transforms: cursor, - fold_chunks, + buffer_chunks, inlay_chunks: None, - fold_chunk: None, + buffer_chunk: None, output_offset: range.start, max_output_offset: range.end, highlight_style: inlay_highlight_style, @@ -761,7 +750,7 @@ impl InlaySnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(Default::default()..self.len(), false, None, None) + self.chunks(Default::default()..self.len(), false, None) .map(|chunk| chunk.text) .collect() } @@ -769,10 +758,7 @@ impl InlaySnapshot { fn check_invariants(&self) { #[cfg(any(debug_assertions, feature = "test-support"))] { - assert_eq!( - self.transforms.summary().input, - self.fold_snapshot.text_summary() - ); + assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); } } } @@ -800,7 +786,7 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { #[cfg(test)] mod tests { use super::*; - use crate::{display_map::fold_map::FoldMap, MultiBuffer}; + use crate::MultiBuffer; use gpui::AppContext; use rand::prelude::*; use settings::SettingsStore; @@ -812,8 +798,7 @@ mod tests { fn test_basic_inlays(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abcdefghi", cx); let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abcdefghi"); let mut next_inlay_id = 0; @@ -829,27 +814,27 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 0)), + inlay_snapshot.to_inlay_point(Point::new(0, 0)), InlayPoint::new(0, 0) ); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 1)), + inlay_snapshot.to_inlay_point(Point::new(0, 1)), InlayPoint::new(0, 1) ); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 2)), + inlay_snapshot.to_inlay_point(Point::new(0, 2)), InlayPoint::new(0, 2) ); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 3)), + inlay_snapshot.to_inlay_point(Point::new(0, 3)), InlayPoint::new(0, 3) ); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 4)), + inlay_snapshot.to_inlay_point(Point::new(0, 4)), InlayPoint::new(0, 9) ); assert_eq!( - inlay_snapshot.to_inlay_point(FoldPoint::new(0, 5)), + inlay_snapshot.to_inlay_point(Point::new(0, 5)), InlayPoint::new(0, 10) ); assert_eq!( @@ -881,20 +866,18 @@ mod tests { buffer.update(cx, |buffer, cx| { buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx) }); - let (fold_snapshot, fold_edits) = fold_map.read( + let (inlay_snapshot, _) = inlay_map.sync( buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); // An edit surrounding the inlay should invalidate it. buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx)); - let (fold_snapshot, fold_edits) = fold_map.read( + let (inlay_snapshot, _) = inlay_map.sync( buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); let (inlay_snapshot, _) = inlay_map.splice( @@ -920,11 +903,10 @@ mod tests { // Edits ending where the inlay starts should not move it if it has a left bias. buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx)); - let (fold_snapshot, fold_edits) = fold_map.read( + let (inlay_snapshot, _) = inlay_map.sync( buffer.read(cx).snapshot(cx), buffer_edits.consume().into_inner(), ); - let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits); assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. @@ -936,8 +918,7 @@ mod tests { #[gpui::test] fn test_buffer_rows(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); - let (_, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); let (inlay_snapshot, _) = inlay_map.splice( @@ -993,27 +974,20 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); log::info!("buffer text: {:?}", buffer_snapshot.text()); - let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let mut next_inlay_id = 0; for _ in 0..operations { - let mut fold_edits = Patch::default(); let mut inlay_edits = Patch::default(); let mut prev_inlay_text = inlay_snapshot.text(); let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=29 => { + 0..=50 => { let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); log::info!("mutated text: {:?}", snapshot.text()); inlay_edits = Patch::new(edits); } - 30..=59 => { - for (_, edits) in fold_map.randomly_mutate(&mut rng) { - fold_edits = fold_edits.compose(edits); - } - } _ => buffer.update(cx, |buffer, cx| { let subscription = buffer.subscribe(); let edit_count = rng.gen_range(1..=5); @@ -1025,17 +999,12 @@ mod tests { }), }; - let (new_fold_snapshot, new_fold_edits) = - fold_map.read(buffer_snapshot.clone(), buffer_edits); - fold_snapshot = new_fold_snapshot; - fold_edits = fold_edits.compose(new_fold_edits); let (new_inlay_snapshot, new_inlay_edits) = - inlay_map.sync(fold_snapshot.clone(), fold_edits.into_inner()); + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); inlay_snapshot = new_inlay_snapshot; inlay_edits = inlay_edits.compose(new_inlay_edits); log::info!("buffer text: {:?}", buffer_snapshot.text()); - log::info!("folds text: {:?}", fold_snapshot.text()); log::info!("inlay text: {:?}", inlay_snapshot.text()); let inlays = inlay_map @@ -1044,14 +1013,13 @@ mod tests { .filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) .map(|inlay| { let buffer_point = inlay.position.to_point(&buffer_snapshot); - let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&fold_snapshot); - (fold_offset, inlay.clone()) + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + (buffer_offset, inlay.clone()) }) .collect::>(); - let mut expected_text = Rope::from(fold_snapshot.text().as_str()); + let mut expected_text = Rope::from(buffer_snapshot.text().as_str()); for (offset, inlay) in inlays.into_iter().rev() { - expected_text.replace(offset.0..offset.0, &inlay.text.to_string()); + expected_text.replace(offset..offset, &inlay.text.to_string()); } assert_eq!(inlay_snapshot.text(), expected_text.to_string()); @@ -1078,7 +1046,7 @@ mod tests { start = expected_text.clip_offset(start, Bias::Right); let actual_text = inlay_snapshot - .chunks(InlayOffset(start)..InlayOffset(end), false, None, None) + .chunks(InlayOffset(start)..InlayOffset(end), false, None) .map(|chunk| chunk.text) .collect::(); assert_eq!( @@ -1123,11 +1091,11 @@ mod tests { inlay_offset ); assert_eq!( - inlay_snapshot.to_inlay_point(inlay_snapshot.to_fold_point(inlay_point)), + inlay_snapshot.to_inlay_point(inlay_snapshot.to_buffer_point(inlay_point)), inlay_snapshot.clip_point(inlay_point, Bias::Left), - "to_fold_point({:?}) = {:?}", + "to_buffer_point({:?}) = {:?}", inlay_point, - inlay_snapshot.to_fold_point(inlay_point), + inlay_snapshot.to_buffer_point(inlay_point), ); let mut bytes = [0; 4]; diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs deleted file mode 100644 index b23f172bcaa6d6cf22505bdd9715d88e6f874217..0000000000000000000000000000000000000000 --- a/crates/editor/src/display_map/suggestion_map.rs +++ /dev/null @@ -1,875 +0,0 @@ -use super::{ - fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, - TextHighlights, -}; -use crate::{MultiBufferSnapshot, ToPoint}; -use gpui::fonts::HighlightStyle; -use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary}; -use parking_lot::Mutex; -use std::{ - cmp, - ops::{Add, AddAssign, Range, Sub}, -}; -use util::post_inc; - -pub type SuggestionEdit = Edit; - -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct SuggestionOffset(pub usize); - -impl Add for SuggestionOffset { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Sub for SuggestionOffset { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl AddAssign for SuggestionOffset { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0; - } -} - -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct SuggestionPoint(pub Point); - -impl SuggestionPoint { - pub fn new(row: u32, column: u32) -> Self { - Self(Point::new(row, column)) - } - - pub fn row(self) -> u32 { - self.0.row - } - - pub fn column(self) -> u32 { - self.0.column - } -} - -#[derive(Clone, Debug)] -pub struct Suggestion { - pub position: T, - pub text: Rope, -} - -pub struct SuggestionMap(Mutex); - -impl SuggestionMap { - pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) { - let snapshot = SuggestionSnapshot { - fold_snapshot, - suggestion: None, - version: 0, - }; - (Self(Mutex::new(snapshot.clone())), snapshot) - } - - pub fn replace( - &self, - new_suggestion: Option>, - fold_snapshot: FoldSnapshot, - fold_edits: Vec, - ) -> ( - SuggestionSnapshot, - Vec, - Option>, - ) - where - T: ToPoint, - { - let new_suggestion = new_suggestion.map(|new_suggestion| { - let buffer_point = new_suggestion - .position - .to_point(fold_snapshot.buffer_snapshot()); - let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&fold_snapshot); - Suggestion { - position: fold_offset, - text: new_suggestion.text, - } - }); - - let (_, edits) = self.sync(fold_snapshot, fold_edits); - let mut snapshot = self.0.lock(); - - let mut patch = Patch::new(edits); - let old_suggestion = snapshot.suggestion.take(); - if let Some(suggestion) = &old_suggestion { - patch = patch.compose([SuggestionEdit { - old: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), - new: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0), - }]); - } - - if let Some(suggestion) = new_suggestion.as_ref() { - patch = patch.compose([SuggestionEdit { - old: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0), - new: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), - }]); - } - - snapshot.suggestion = new_suggestion; - snapshot.version += 1; - (snapshot.clone(), patch.into_inner(), old_suggestion) - } - - pub fn sync( - &self, - fold_snapshot: FoldSnapshot, - fold_edits: Vec, - ) -> (SuggestionSnapshot, Vec) { - let mut snapshot = self.0.lock(); - - if snapshot.fold_snapshot.version != fold_snapshot.version { - snapshot.version += 1; - } - - let mut suggestion_edits = Vec::new(); - - let mut suggestion_old_len = 0; - let mut suggestion_new_len = 0; - for fold_edit in fold_edits { - let start = fold_edit.new.start; - let end = FoldOffset(start.0 + fold_edit.old_len().0); - if let Some(suggestion) = snapshot.suggestion.as_mut() { - if end <= suggestion.position { - suggestion.position.0 += fold_edit.new_len().0; - suggestion.position.0 -= fold_edit.old_len().0; - } else if start > suggestion.position { - suggestion_old_len = suggestion.text.len(); - suggestion_new_len = suggestion_old_len; - } else { - suggestion_old_len = suggestion.text.len(); - snapshot.suggestion.take(); - suggestion_edits.push(SuggestionEdit { - old: SuggestionOffset(fold_edit.old.start.0) - ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), - new: SuggestionOffset(fold_edit.new.start.0) - ..SuggestionOffset(fold_edit.new.end.0), - }); - continue; - } - } - - suggestion_edits.push(SuggestionEdit { - old: SuggestionOffset(fold_edit.old.start.0 + suggestion_old_len) - ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), - new: SuggestionOffset(fold_edit.new.start.0 + suggestion_new_len) - ..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len), - }); - } - snapshot.fold_snapshot = fold_snapshot; - - (snapshot.clone(), suggestion_edits) - } - - pub fn has_suggestion(&self) -> bool { - let snapshot = self.0.lock(); - snapshot.suggestion.is_some() - } -} - -#[derive(Clone)] -pub struct SuggestionSnapshot { - pub fold_snapshot: FoldSnapshot, - pub suggestion: Option>, - pub version: usize, -} - -impl SuggestionSnapshot { - pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.fold_snapshot.buffer_snapshot() - } - - pub fn max_point(&self) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_point = suggestion.position.to_point(&self.fold_snapshot); - let mut max_point = suggestion_point.0; - max_point += suggestion.text.max_point(); - max_point += self.fold_snapshot.max_point().0 - suggestion_point.0; - SuggestionPoint(max_point) - } else { - SuggestionPoint(self.fold_snapshot.max_point().0) - } - } - - pub fn len(&self) -> SuggestionOffset { - if let Some(suggestion) = self.suggestion.as_ref() { - let mut len = suggestion.position.0; - len += suggestion.text.len(); - len += self.fold_snapshot.len().0 - suggestion.position.0; - SuggestionOffset(len) - } else { - SuggestionOffset(self.fold_snapshot.len().0) - } - } - - pub fn line_len(&self, row: u32) -> u32 { - if let Some(suggestion) = &self.suggestion { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if row < suggestion_start.row { - self.fold_snapshot.line_len(row) - } else if row > suggestion_end.row { - self.fold_snapshot - .line_len(suggestion_start.row + (row - suggestion_end.row)) - } else { - let mut result = suggestion.text.line_len(row - suggestion_start.row); - if row == suggestion_start.row { - result += suggestion_start.column; - } - if row == suggestion_end.row { - result += - self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column; - } - result - } - } else { - self.fold_snapshot.line_len(row) - } - } - - pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - if point.0 <= suggestion_start { - SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) - } else if point.0 > suggestion_end { - let fold_point = self.fold_snapshot.clip_point( - FoldPoint(suggestion_start + (point.0 - suggestion_end)), - bias, - ); - let suggestion_point = suggestion_end + (fold_point.0 - suggestion_start); - if bias == Bias::Left && suggestion_point == suggestion_end { - SuggestionPoint(suggestion_start) - } else { - SuggestionPoint(suggestion_point) - } - } else if bias == Bias::Left || suggestion_start == self.fold_snapshot.max_point().0 { - SuggestionPoint(suggestion_start) - } else { - let fold_point = if self.fold_snapshot.line_len(suggestion_start.row) - > suggestion_start.column - { - FoldPoint(suggestion_start + Point::new(0, 1)) - } else { - FoldPoint(suggestion_start + Point::new(1, 0)) - }; - let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias); - SuggestionPoint(suggestion_end + (clipped_fold_point.0 - suggestion_start)) - } - } else { - SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) - } - } - - pub fn to_offset(&self, point: SuggestionPoint) -> SuggestionOffset { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if point.0 <= suggestion_start { - SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) - } else if point.0 > suggestion_end { - let fold_offset = FoldPoint(suggestion_start + (point.0 - suggestion_end)) - .to_offset(&self.fold_snapshot); - SuggestionOffset(fold_offset.0 + suggestion.text.len()) - } else { - let offset_in_suggestion = - suggestion.text.point_to_offset(point.0 - suggestion_start); - SuggestionOffset(suggestion.position.0 + offset_in_suggestion) - } - } else { - SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) - } - } - - pub fn to_point(&self, offset: SuggestionOffset) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_point_start = suggestion.position.to_point(&self.fold_snapshot).0; - if offset.0 <= suggestion.position.0 { - SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) - } else if offset.0 > (suggestion.position.0 + suggestion.text.len()) { - let fold_point = FoldOffset(offset.0 - suggestion.text.len()) - .to_point(&self.fold_snapshot) - .0; - - SuggestionPoint( - suggestion_point_start - + suggestion.text.max_point() - + (fold_point - suggestion_point_start), - ) - } else { - let point_in_suggestion = suggestion - .text - .offset_to_point(offset.0 - suggestion.position.0); - SuggestionPoint(suggestion_point_start + point_in_suggestion) - } - } else { - SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) - } - } - - pub fn to_fold_point(&self, point: SuggestionPoint) -> FoldPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if point.0 <= suggestion_start { - FoldPoint(point.0) - } else if point.0 > suggestion_end { - FoldPoint(suggestion_start + (point.0 - suggestion_end)) - } else { - FoldPoint(suggestion_start) - } - } else { - FoldPoint(point.0) - } - } - - pub fn to_suggestion_point(&self, point: FoldPoint) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - - if point.0 <= suggestion_start { - SuggestionPoint(point.0) - } else { - let suggestion_end = suggestion_start + suggestion.text.max_point(); - SuggestionPoint(suggestion_end + (point.0 - suggestion_start)) - } - } else { - SuggestionPoint(point.0) - } - } - - pub fn text_summary(&self) -> TextSummary { - self.text_summary_for_range(Default::default()..self.max_point()) - } - - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - let mut summary = TextSummary::default(); - - let prefix_range = - cmp::min(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_start); - if prefix_range.start < prefix_range.end { - summary += self.fold_snapshot.text_summary_for_range( - FoldPoint(prefix_range.start)..FoldPoint(prefix_range.end), - ); - } - - let suggestion_range = - cmp::max(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_end); - if suggestion_range.start < suggestion_range.end { - let point_range = suggestion_range.start - suggestion_start - ..suggestion_range.end - suggestion_start; - let offset_range = suggestion.text.point_to_offset(point_range.start) - ..suggestion.text.point_to_offset(point_range.end); - summary += suggestion - .text - .cursor(offset_range.start) - .summary::(offset_range.end); - } - - let suffix_range = cmp::max(range.start.0, suggestion_end)..range.end.0; - if suffix_range.start < suffix_range.end { - let start = suggestion_start + (suffix_range.start - suggestion_end); - let end = suggestion_start + (suffix_range.end - suggestion_end); - summary += self - .fold_snapshot - .text_summary_for_range(FoldPoint(start)..FoldPoint(end)); - } - - summary - } else { - self.fold_snapshot - .text_summary_for_range(FoldPoint(range.start.0)..FoldPoint(range.end.0)) - } - } - - pub fn chars_at(&self, start: SuggestionPoint) -> impl '_ + Iterator { - let start = self.to_offset(start); - self.chunks(start..self.len(), false, None, None) - .flat_map(|chunk| chunk.text.chars()) - } - - pub fn chunks<'a>( - &'a self, - range: Range, - language_aware: bool, - text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, - ) -> SuggestionChunks<'a> { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_range = - suggestion.position.0..suggestion.position.0 + suggestion.text.len(); - - let prefix_chunks = if range.start.0 < suggestion_range.start { - Some(self.fold_snapshot.chunks( - FoldOffset(range.start.0) - ..cmp::min(FoldOffset(suggestion_range.start), FoldOffset(range.end.0)), - language_aware, - text_highlights, - )) - } else { - None - }; - - let clipped_suggestion_range = cmp::max(range.start.0, suggestion_range.start) - ..cmp::min(range.end.0, suggestion_range.end); - let suggestion_chunks = if clipped_suggestion_range.start < clipped_suggestion_range.end - { - let start = clipped_suggestion_range.start - suggestion_range.start; - let end = clipped_suggestion_range.end - suggestion_range.start; - Some(suggestion.text.chunks_in_range(start..end)) - } else { - None - }; - - let suffix_chunks = if range.end.0 > suggestion_range.end { - let start = cmp::max(suggestion_range.end, range.start.0) - suggestion_range.len(); - let end = range.end.0 - suggestion_range.len(); - Some(self.fold_snapshot.chunks( - FoldOffset(start)..FoldOffset(end), - language_aware, - text_highlights, - )) - } else { - None - }; - - SuggestionChunks { - prefix_chunks, - suggestion_chunks, - suffix_chunks, - highlight_style: suggestion_highlight, - } - } else { - SuggestionChunks { - prefix_chunks: Some(self.fold_snapshot.chunks( - FoldOffset(range.start.0)..FoldOffset(range.end.0), - language_aware, - text_highlights, - )), - suggestion_chunks: None, - suffix_chunks: None, - highlight_style: None, - } - } - } - - pub fn buffer_rows<'a>(&'a self, row: u32) -> SuggestionBufferRows<'a> { - let suggestion_range = if let Some(suggestion) = self.suggestion.as_ref() { - let start = suggestion.position.to_point(&self.fold_snapshot).0; - let end = start + suggestion.text.max_point(); - start.row..end.row - } else { - u32::MAX..u32::MAX - }; - - let fold_buffer_rows = if row <= suggestion_range.start { - self.fold_snapshot.buffer_rows(row) - } else if row > suggestion_range.end { - self.fold_snapshot - .buffer_rows(row - (suggestion_range.end - suggestion_range.start)) - } else { - let mut rows = self.fold_snapshot.buffer_rows(suggestion_range.start); - rows.next(); - rows - }; - - SuggestionBufferRows { - current_row: row, - suggestion_row_start: suggestion_range.start, - suggestion_row_end: suggestion_range.end, - fold_buffer_rows, - } - } - - #[cfg(test)] - pub fn text(&self) -> String { - self.chunks(Default::default()..self.len(), false, None, None) - .map(|chunk| chunk.text) - .collect() - } -} - -pub struct SuggestionChunks<'a> { - prefix_chunks: Option>, - suggestion_chunks: Option>, - suffix_chunks: Option>, - highlight_style: Option, -} - -impl<'a> Iterator for SuggestionChunks<'a> { - type Item = Chunk<'a>; - - fn next(&mut self) -> Option { - if let Some(chunks) = self.prefix_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(chunk); - } else { - self.prefix_chunks = None; - } - } - - if let Some(chunks) = self.suggestion_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(Chunk { - text: chunk, - highlight_style: self.highlight_style, - ..Default::default() - }); - } else { - self.suggestion_chunks = None; - } - } - - if let Some(chunks) = self.suffix_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(chunk); - } else { - self.suffix_chunks = None; - } - } - - None - } -} - -#[derive(Clone)] -pub struct SuggestionBufferRows<'a> { - current_row: u32, - suggestion_row_start: u32, - suggestion_row_end: u32, - fold_buffer_rows: FoldBufferRows<'a>, -} - -impl<'a> Iterator for SuggestionBufferRows<'a> { - type Item = Option; - - fn next(&mut self) -> Option { - let row = post_inc(&mut self.current_row); - if row <= self.suggestion_row_start || row > self.suggestion_row_end { - self.fold_buffer_rows.next() - } else { - Some(None) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{display_map::fold_map::FoldMap, MultiBuffer}; - use gpui::AppContext; - use rand::{prelude::StdRng, Rng}; - use settings::SettingsStore; - use std::{ - env, - ops::{Bound, RangeBounds}, - }; - - #[gpui::test] - fn test_basic(cx: &mut AppContext) { - let buffer = MultiBuffer::build_simple("abcdefghi", cx); - let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - assert_eq!(suggestion_snapshot.text(), "abcdefghi"); - - let (suggestion_snapshot, _, _) = suggestion_map.replace( - Some(Suggestion { - position: 3, - text: "123\n456".into(), - }), - fold_snapshot, - Default::default(), - ); - assert_eq!(suggestion_snapshot.text(), "abc123\n456defghi"); - - buffer.update(cx, |buffer, cx| { - buffer.edit( - [(0..0, "ABC"), (3..3, "DEF"), (4..4, "GHI"), (9..9, "JKL")], - None, - cx, - ) - }); - let (fold_snapshot, fold_edits) = fold_map.read( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); - assert_eq!(suggestion_snapshot.text(), "ABCabcDEF123\n456dGHIefghiJKL"); - - let (mut fold_map_writer, _, _) = - fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); - let (fold_snapshot, fold_edits) = fold_map_writer.fold([0..3]); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); - assert_eq!(suggestion_snapshot.text(), "⋯abcDEF123\n456dGHIefghiJKL"); - - let (mut fold_map_writer, _, _) = - fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); - let (fold_snapshot, fold_edits) = fold_map_writer.fold([6..10]); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); - assert_eq!(suggestion_snapshot.text(), "⋯abc⋯GHIefghiJKL"); - } - - #[gpui::test(iterations = 100)] - fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) { - init_test(cx); - - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - - let len = rng.gen_range(0..30); - let buffer = if rng.gen() { - let text = util::RandomCharIter::new(&mut rng) - .take(len) - .collect::(); - MultiBuffer::build_simple(&text, cx) - } else { - MultiBuffer::build_random(&mut rng, cx) - }; - let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - log::info!("buffer text: {:?}", buffer_snapshot.text()); - - let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - - for _ in 0..operations { - let mut suggestion_edits = Patch::default(); - - let mut prev_suggestion_text = suggestion_snapshot.text(); - let mut buffer_edits = Vec::new(); - match rng.gen_range(0..=100) { - 0..=29 => { - let (_, edits) = suggestion_map.randomly_mutate(&mut rng); - suggestion_edits = suggestion_edits.compose(edits); - } - 30..=59 => { - for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - fold_snapshot = new_fold_snapshot; - let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_edits = suggestion_edits.compose(edits); - } - } - _ => buffer.update(cx, |buffer, cx| { - let subscription = buffer.subscribe(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_mutate(&mut rng, edit_count, cx); - buffer_snapshot = buffer.snapshot(cx); - let edits = subscription.consume().into_inner(); - log::info!("editing {:?}", edits); - buffer_edits.extend(edits); - }), - }; - - let (new_fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), buffer_edits); - fold_snapshot = new_fold_snapshot; - let (new_suggestion_snapshot, edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits = suggestion_edits.compose(edits); - - log::info!("buffer text: {:?}", buffer_snapshot.text()); - log::info!("folds text: {:?}", fold_snapshot.text()); - log::info!("suggestions text: {:?}", suggestion_snapshot.text()); - - let mut expected_text = Rope::from(fold_snapshot.text().as_str()); - let mut expected_buffer_rows = fold_snapshot.buffer_rows(0).collect::>(); - if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { - expected_text.replace( - suggestion.position.0..suggestion.position.0, - &suggestion.text.to_string(), - ); - let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - expected_buffer_rows.splice( - (suggestion_start.row + 1) as usize..(suggestion_start.row + 1) as usize, - (0..suggestion_end.row - suggestion_start.row).map(|_| None), - ); - } - assert_eq!(suggestion_snapshot.text(), expected_text.to_string()); - for row_start in 0..expected_buffer_rows.len() { - assert_eq!( - suggestion_snapshot - .buffer_rows(row_start as u32) - .collect::>(), - &expected_buffer_rows[row_start..], - "incorrect buffer rows starting at {}", - row_start - ); - } - - for _ in 0..5 { - let mut end = rng.gen_range(0..=suggestion_snapshot.len().0); - end = expected_text.clip_offset(end, Bias::Right); - let mut start = rng.gen_range(0..=end); - start = expected_text.clip_offset(start, Bias::Right); - - let actual_text = suggestion_snapshot - .chunks( - SuggestionOffset(start)..SuggestionOffset(end), - false, - None, - None, - ) - .map(|chunk| chunk.text) - .collect::(); - assert_eq!( - actual_text, - expected_text.slice(start..end).to_string(), - "incorrect text in range {:?}", - start..end - ); - - let start_point = SuggestionPoint(expected_text.offset_to_point(start)); - let end_point = SuggestionPoint(expected_text.offset_to_point(end)); - assert_eq!( - suggestion_snapshot.text_summary_for_range(start_point..end_point), - expected_text.slice(start..end).summary() - ); - } - - for edit in suggestion_edits.into_inner() { - prev_suggestion_text.replace_range( - edit.new.start.0..edit.new.start.0 + edit.old_len().0, - &suggestion_snapshot.text()[edit.new.start.0..edit.new.end.0], - ); - } - assert_eq!(prev_suggestion_text, suggestion_snapshot.text()); - - assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0); - assert_eq!(expected_text.len(), suggestion_snapshot.len().0); - - let mut suggestion_point = SuggestionPoint::default(); - let mut suggestion_offset = SuggestionOffset::default(); - for ch in expected_text.chars() { - assert_eq!( - suggestion_snapshot.to_offset(suggestion_point), - suggestion_offset, - "invalid to_offset({:?})", - suggestion_point - ); - assert_eq!( - suggestion_snapshot.to_point(suggestion_offset), - suggestion_point, - "invalid to_point({:?})", - suggestion_offset - ); - assert_eq!( - suggestion_snapshot - .to_suggestion_point(suggestion_snapshot.to_fold_point(suggestion_point)), - suggestion_snapshot.clip_point(suggestion_point, Bias::Left), - ); - - let mut bytes = [0; 4]; - for byte in ch.encode_utf8(&mut bytes).as_bytes() { - suggestion_offset.0 += 1; - if *byte == b'\n' { - suggestion_point.0 += Point::new(1, 0); - } else { - suggestion_point.0 += Point::new(0, 1); - } - - let clipped_left_point = - suggestion_snapshot.clip_point(suggestion_point, Bias::Left); - let clipped_right_point = - suggestion_snapshot.clip_point(suggestion_point, Bias::Right); - assert!( - clipped_left_point <= clipped_right_point, - "clipped left point {:?} is greater than clipped right point {:?}", - clipped_left_point, - clipped_right_point - ); - assert_eq!( - clipped_left_point.0, - expected_text.clip_point(clipped_left_point.0, Bias::Left) - ); - assert_eq!( - clipped_right_point.0, - expected_text.clip_point(clipped_right_point.0, Bias::Right) - ); - assert!(clipped_left_point <= suggestion_snapshot.max_point()); - assert!(clipped_right_point <= suggestion_snapshot.max_point()); - - if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - let invalid_range = ( - Bound::Excluded(suggestion_start), - Bound::Included(suggestion_end), - ); - assert!( - !invalid_range.contains(&clipped_left_point.0), - "clipped left point {:?} is inside invalid suggestion range {:?}", - clipped_left_point, - invalid_range - ); - assert!( - !invalid_range.contains(&clipped_right_point.0), - "clipped right point {:?} is inside invalid suggestion range {:?}", - clipped_right_point, - invalid_range - ); - } - } - } - } - } - - fn init_test(cx: &mut AppContext) { - cx.set_global(SettingsStore::test(cx)); - theme::init((), cx); - } - - impl SuggestionMap { - pub fn randomly_mutate( - &self, - rng: &mut impl Rng, - ) -> (SuggestionSnapshot, Vec) { - let fold_snapshot = self.0.lock().fold_snapshot.clone(); - let new_suggestion = if rng.gen_bool(0.3) { - None - } else { - let index = rng.gen_range(0..=fold_snapshot.buffer_snapshot().len()); - let len = rng.gen_range(0..30); - Some(Suggestion { - position: index, - text: util::RandomCharIter::new(rng) - .take(len) - .filter(|ch| *ch != '\r') - .collect::() - .as_str() - .into(), - }) - }; - - log::info!("replacing suggestion with {:?}", new_suggestion); - let (snapshot, edits, _) = - self.replace(new_suggestion, fold_snapshot, Default::default()); - (snapshot, edits) - } - } -} diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 0deb0f888ff6334cdef3a451ae16a5d9b4e331a4..9157caace496e72597e70ac039e7797672122dbc 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,5 +1,5 @@ use super::{ - inlay_map::{self, InlayChunks, InlayEdit, InlayPoint, InlaySnapshot}, + fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot}, TextHighlights, }; use crate::MultiBufferSnapshot; @@ -14,9 +14,9 @@ const MAX_EXPANSION_COLUMN: u32 = 256; pub struct TabMap(Mutex); impl TabMap { - pub fn new(input: InlaySnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { let snapshot = TabSnapshot { - inlay_snapshot: input, + fold_snapshot, tab_size, max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, @@ -32,45 +32,42 @@ impl TabMap { pub fn sync( &self, - inlay_snapshot: InlaySnapshot, - mut suggestion_edits: Vec, + fold_snapshot: FoldSnapshot, + mut fold_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); let mut new_snapshot = TabSnapshot { - inlay_snapshot, + fold_snapshot, tab_size, max_expansion_column: old_snapshot.max_expansion_column, version: old_snapshot.version, }; - if old_snapshot.inlay_snapshot.version != new_snapshot.inlay_snapshot.version { + if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version { new_snapshot.version += 1; } - let mut tab_edits = Vec::with_capacity(suggestion_edits.len()); + let mut tab_edits = Vec::with_capacity(fold_edits.len()); if old_snapshot.tab_size == new_snapshot.tab_size { // Expand each edit to include the next tab on the same line as the edit, // and any subsequent tabs on that line that moved across the tab expansion // boundary. - for suggestion_edit in &mut suggestion_edits { - let old_end = old_snapshot - .inlay_snapshot - .to_point(suggestion_edit.old.end); - let old_end_row_successor_offset = old_snapshot.inlay_snapshot.to_offset(cmp::min( - InlayPoint::new(old_end.row() + 1, 0), - old_snapshot.inlay_snapshot.max_point(), - )); - let new_end = new_snapshot - .inlay_snapshot - .to_point(suggestion_edit.new.end); + for fold_edit in &mut fold_edits { + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let old_end_row_successor_offset = cmp::min( + FoldPoint::new(old_end.row() + 1, 0), + old_snapshot.fold_snapshot.max_point(), + ) + .to_offset(&old_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); let mut offset_from_edit = 0; let mut first_tab_offset = None; let mut last_tab_with_changed_expansion_offset = None; - 'outer: for chunk in old_snapshot.inlay_snapshot.chunks( - suggestion_edit.old.end..old_end_row_successor_offset, + 'outer: for chunk in old_snapshot.fold_snapshot.chunks( + fold_edit.old.end..old_end_row_successor_offset, false, None, None, @@ -101,39 +98,31 @@ impl TabMap { } if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { - suggestion_edit.old.end.0 += offset as usize + 1; - suggestion_edit.new.end.0 += offset as usize + 1; + fold_edit.old.end.0 += offset as usize + 1; + fold_edit.new.end.0 += offset as usize + 1; } } // Combine any edits that overlap due to the expansion. let mut ix = 1; - while ix < suggestion_edits.len() { - let (prev_edits, next_edits) = suggestion_edits.split_at_mut(ix); + while ix < fold_edits.len() { + let (prev_edits, next_edits) = fold_edits.split_at_mut(ix); let prev_edit = prev_edits.last_mut().unwrap(); let edit = &next_edits[0]; if prev_edit.old.end >= edit.old.start { prev_edit.old.end = edit.old.end; prev_edit.new.end = edit.new.end; - suggestion_edits.remove(ix); + fold_edits.remove(ix); } else { ix += 1; } } - for suggestion_edit in suggestion_edits { - let old_start = old_snapshot - .inlay_snapshot - .to_point(suggestion_edit.old.start); - let old_end = old_snapshot - .inlay_snapshot - .to_point(suggestion_edit.old.end); - let new_start = new_snapshot - .inlay_snapshot - .to_point(suggestion_edit.new.start); - let new_end = new_snapshot - .inlay_snapshot - .to_point(suggestion_edit.new.end); + for fold_edit in fold_edits { + let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); tab_edits.push(TabEdit { old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end), @@ -154,7 +143,7 @@ impl TabMap { #[derive(Clone)] pub struct TabSnapshot { - pub inlay_snapshot: InlaySnapshot, + pub fold_snapshot: FoldSnapshot, pub tab_size: NonZeroU32, pub max_expansion_column: u32, pub version: usize, @@ -162,13 +151,13 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.inlay_snapshot.buffer_snapshot() + &self.fold_snapshot.inlay_snapshot.buffer } pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.to_tab_point(InlayPoint::new(row, self.inlay_snapshot.line_len(row))) + self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row))) .0 .column } else { @@ -181,10 +170,10 @@ impl TabSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let input_start = self.to_inlay_point(range.start, Bias::Left).0; - let input_end = self.to_inlay_point(range.end, Bias::Right).0; + let input_start = self.to_fold_point(range.start, Bias::Left).0; + let input_end = self.to_fold_point(range.end, Bias::Right).0; let input_summary = self - .inlay_snapshot + .fold_snapshot .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; @@ -234,15 +223,16 @@ impl TabSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + inlay_highlights: Option, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = - self.to_inlay_point(range.start, Bias::Left); + self.to_fold_point(range.start, Bias::Left); let input_column = input_start.column(); - let input_start = self.inlay_snapshot.to_offset(input_start); + let input_start = input_start.to_offset(&self.fold_snapshot); let input_end = self - .inlay_snapshot - .to_offset(self.to_inlay_point(range.end, Bias::Right).0); + .to_fold_point(range.end, Bias::Right) + .0 + .to_offset(&self.fold_snapshot); let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { range.end.column() - range.start.column() } else { @@ -250,11 +240,11 @@ impl TabSnapshot { }; TabChunks { - inlay_chunks: self.inlay_snapshot.chunks( + fold_chunks: self.fold_snapshot.chunks( input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + inlay_highlights, ), input_column, column: expanded_char_column, @@ -271,8 +261,8 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> inlay_map::InlayBufferRows<'_> { - self.inlay_snapshot.buffer_rows(row) + pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> { + self.fold_snapshot.buffer_rows(row) } #[cfg(test)] @@ -283,48 +273,46 @@ impl TabSnapshot { } pub fn max_point(&self) -> TabPoint { - self.to_tab_point(self.inlay_snapshot.max_point()) + self.to_tab_point(self.fold_snapshot.max_point()) } pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { self.to_tab_point( - self.inlay_snapshot - .clip_point(self.to_inlay_point(point, bias).0, bias), + self.fold_snapshot + .clip_point(self.to_fold_point(point, bias).0, bias), ) } - pub fn to_tab_point(&self, input: InlayPoint) -> TabPoint { - let chars = self - .inlay_snapshot - .chars_at(InlayPoint::new(input.row(), 0)); + pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0)); let expanded = self.expand_tabs(chars, input.column()); TabPoint::new(input.row(), expanded) } - pub fn to_inlay_point(&self, output: TabPoint, bias: Bias) -> (InlayPoint, u32, u32) { - let chars = self - .inlay_snapshot - .chars_at(InlayPoint::new(output.row(), 0)); + pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); let expanded = output.column(); let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( - InlayPoint::new(output.row(), collapsed as u32), + FoldPoint::new(output.row(), collapsed as u32), expanded_char_column, to_next_stop, ) } pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { - let fold_point = self.inlay_snapshot.fold_snapshot.to_fold_point(point, bias); - let inlay_point = self.inlay_snapshot.to_inlay_point(fold_point); - self.to_tab_point(inlay_point) + let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + self.to_tab_point(fold_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { - let inlay_point = self.to_inlay_point(point, bias).0; - let fold_point = self.inlay_snapshot.to_fold_point(inlay_point); - fold_point.to_buffer_point(&self.inlay_snapshot.fold_snapshot) + let fold_point = self.to_fold_point(point, bias).0; + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + self.fold_snapshot + .inlay_snapshot + .to_buffer_point(inlay_point) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { @@ -483,7 +471,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { const SPACES: &str = " "; pub struct TabChunks<'a> { - inlay_chunks: InlayChunks<'a>, + fold_chunks: FoldChunks<'a>, chunk: Chunk<'a>, column: u32, max_expansion_column: u32, @@ -499,7 +487,7 @@ impl<'a> Iterator for TabChunks<'a> { fn next(&mut self) -> Option { if self.chunk.text.is_empty() { - if let Some(chunk) = self.inlay_chunks.next() { + if let Some(chunk) = self.fold_chunks.next() { self.chunk = chunk; if self.inside_leading_tab { self.chunk.text = &self.chunk.text[1..]; @@ -576,9 +564,9 @@ mod tests { fn test_expand_tabs(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple("", cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); @@ -593,9 +581,9 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), output); @@ -619,16 +607,16 @@ mod tests { let input_point = Point::new(0, ix as u32); let output_point = Point::new(0, output.find(c).unwrap() as u32); assert_eq!( - tab_snapshot.to_tab_point(InlayPoint(input_point)), + tab_snapshot.to_tab_point(FoldPoint(input_point)), TabPoint(output_point), "to_tab_point({input_point:?})" ); assert_eq!( tab_snapshot - .to_inlay_point(TabPoint(output_point), Bias::Left) + .to_fold_point(TabPoint(output_point), Bias::Left) .0, - InlayPoint(input_point), - "to_suggestion_point({output_point:?})" + FoldPoint(input_point), + "to_fold_point({output_point:?})" ); } } @@ -641,9 +629,9 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), input); @@ -655,9 +643,9 @@ mod tests { let buffer = MultiBuffer::build_simple(&input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, inlay_snapshot) = InlayMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(inlay_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!( chunks(&tab_snapshot, TabPoint::zero()), @@ -714,15 +702,16 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone()); fold_map.randomly_mutate(&mut rng); - let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); + let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (mut inlay_map, _) = InlayMap::new(fold_snapshot.clone()); let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); let text = text::Rope::from(tabs_snapshot.text().as_str()); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 2e2fa449bd33d33b33851073933716789ccde148..5197a2e0de49150f38e37e39ca7e42ba4050c026 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,5 @@ use super::{ - inlay_map::InlayBufferRows, + fold_map::FoldBufferRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, TextHighlights, }; @@ -65,7 +65,7 @@ pub struct WrapChunks<'a> { #[derive(Clone)] pub struct WrapBufferRows<'a> { - input_buffer_rows: InlayBufferRows<'a>, + input_buffer_rows: FoldBufferRows<'a>, input_buffer_row: Option, output_row: u32, soft_wrapped: bool, @@ -575,7 +575,7 @@ impl WrapSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + inlay_highlights: Option, ) -> WrapChunks<'a> { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); @@ -593,7 +593,7 @@ impl WrapSnapshot { input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + inlay_highlights, ), input_chunk: Default::default(), output_position: output_start, @@ -762,13 +762,16 @@ impl WrapSnapshot { let mut prev_fold_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let inlay_point = self.tab_snapshot.to_inlay_point(tab_point, Bias::Left).0; - let fold_point = self.tab_snapshot.inlay_snapshot.to_fold_point(inlay_point); + let fold_point = self.tab_snapshot.to_fold_point(tab_point, Bias::Left).0; if fold_point.row() == prev_fold_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let buffer_point = - fold_point.to_buffer_point(&self.tab_snapshot.inlay_snapshot.fold_snapshot); + let inlay_point = fold_point.to_inlay_point(&self.tab_snapshot.fold_snapshot); + let buffer_point = self + .tab_snapshot + .fold_snapshot + .inlay_snapshot + .to_buffer_point(inlay_point); expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); prev_fold_row = fold_point.row(); } @@ -1083,11 +1086,11 @@ mod tests { }); let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone()); - log::info!("InlaysMap text: {:?}", inlay_snapshot.text()); - let (tab_map, _) = TabMap::new(inlay_snapshot.clone(), tab_size); + let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); @@ -1134,10 +1137,8 @@ mod tests { } 20..=39 => { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(fold_snapshot, fold_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1148,8 +1149,9 @@ mod tests { 40..=59 => { let (inlay_snapshot, inlay_edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1168,11 +1170,12 @@ mod tests { } log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (inlay_snapshot, inlay_edits) = inlay_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (tabs_snapshot, tab_edits) = tab_map.sync(inlay_snapshot, inlay_edits, tab_size); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + log::info!("FoldMap text: {:?}", fold_snapshot.text()); + let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); @@ -1220,7 +1223,7 @@ mod tests { if tab_size.get() == 1 || !wrapped_snapshot .tab_snapshot - .inlay_snapshot + .fold_snapshot .text() .contains('\t') { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 33bf89e45272699592c5c3e4ceb68cc6b34084cd..10c86ceb29c3a534876582e9bddccb49a5d7f064 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -70,11 +70,11 @@ use link_go_to_definition::{ hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, }; use log::error; +use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; -use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ From 05dc672c2a7b6212928c1adcf0d70097262e43f1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 11:34:40 +0300 Subject: [PATCH 099/169] Apply questionable changes to make things compile --- crates/editor/src/display_map/fold_map.rs | 73 +++++++++++++++------- crates/editor/src/display_map/inlay_map.rs | 22 ++++--- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 0cd3817814ad6319e811e52b783ea82d97c520b6..741b79490206b2c7d9bed5f54d14a643205c8082 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -301,7 +301,7 @@ impl FoldMap { fn sync(&self, inlay_snapshot: InlaySnapshot, inlay_edits: Vec) -> Vec { let buffer = &inlay_snapshot.buffer; - let mut snapshot = self.inlay_snapshot.lock(); + let snapshot = self.inlay_snapshot.lock(); let mut new_snapshot = snapshot.clone(); if new_snapshot.version != inlay_snapshot.version { @@ -315,7 +315,14 @@ impl FoldMap { let mut cursor = transforms.cursor::(); cursor.seek(&0, Bias::Right, &()); - while let Some(mut edit) = inlay_edits_iter.next() { + while let Some(inlay_edit) = inlay_edits_iter.next() { + // TODO kb is this right? + let mut edit = Edit { + old: inlay_snapshot.to_buffer_offset(inlay_edit.old.start) + ..inlay_snapshot.to_buffer_offset(inlay_edit.old.end), + new: inlay_snapshot.to_buffer_offset(inlay_edit.new.start) + ..inlay_snapshot.to_buffer_offset(inlay_edit.new.end), + }; new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); edit.new.start -= edit.old.start - cursor.start(); edit.old.start = *cursor.start(); @@ -327,12 +334,18 @@ impl FoldMap { loop { edit.old.end = *cursor.start(); - if let Some(next_edit) = inlay_edits_iter.peek() { - if next_edit.old.start > edit.old.end { + if let Some(next_inlay_edit) = inlay_edits_iter.peek() { + if next_inlay_edit.old.start > inlay_snapshot.to_inlay_offset(edit.old.end) { break; } - let next_edit = inlay_edits_iter.next().unwrap(); + let next_inlay_edit = inlay_edits_iter.next().unwrap(); + let next_edit = Edit { + old: inlay_snapshot.to_buffer_offset(next_inlay_edit.old.start) + ..inlay_snapshot.to_buffer_offset(next_inlay_edit.old.end), + new: inlay_snapshot.to_buffer_offset(next_inlay_edit.new.start) + ..inlay_snapshot.to_buffer_offset(next_inlay_edit.new.end), + }; delta += next_edit.new.len() as isize - next_edit.old.len() as isize; if next_edit.old.end >= edit.old.end { @@ -347,12 +360,12 @@ impl FoldMap { edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - let anchor = buffer.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let anchor = buffer.anchor_before(edit.new.start); let mut folds_cursor = self.folds.cursor::(); folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); let mut folds = iter::from_fn({ - move || { + || { let item = folds_cursor.item().map(|f| { let fold_buffer_start = f.0.start.to_offset(buffer); let fold_buffer_end = f.0.end.to_offset(buffer); @@ -366,11 +379,13 @@ impl FoldMap { }) .peekable(); - while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + while folds.peek().map_or(false, |fold| { + inlay_snapshot.to_buffer_offset(fold.start) < edit.new.end + }) { let mut fold = folds.next().unwrap(); let sum = new_transforms.summary(); - assert!(fold.start >= sum.input.len); + assert!(inlay_snapshot.to_buffer_offset(fold.start) >= sum.input.len); while folds .peek() @@ -382,9 +397,10 @@ impl FoldMap { } } - if fold.start > sum.input.len { - let text_summary = - buffer.text_summary_for_range::(sum.input.len..fold.start); + if inlay_snapshot.to_buffer_offset(fold.start) > sum.input.len { + let text_summary = buffer.text_summary_for_range::( + sum.input.len..inlay_snapshot.to_buffer_offset(fold.start), + ); new_transforms.push( Transform { summary: TransformSummary { @@ -403,7 +419,10 @@ impl FoldMap { Transform { summary: TransformSummary { output: TextSummary::from(output_text), - input: buffer.text_summary_for_range(fold.start..fold.end), + input: inlay_snapshot.text_summary_for_range( + inlay_snapshot.to_point(fold.start) + ..inlay_snapshot.to_point(fold.end), + ), }, output_text: Some(output_text), }, @@ -414,7 +433,8 @@ impl FoldMap { let sum = new_transforms.summary(); if sum.input.len < edit.new.end { - let text_summary = buffer.text_summary_for_range(sum.input.len..edit.new.end); + let text_summary: TextSummary = + buffer.text_summary_for_range(sum.input.len..edit.new.end); new_transforms.push( Transform { summary: TransformSummary { @@ -450,7 +470,13 @@ impl FoldMap { let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); - for mut edit in inlay_edits { + for inlay_edit in inlay_edits { + let mut edit = Edit { + old: inlay_snapshot.to_buffer_offset(inlay_edit.old.start) + ..inlay_snapshot.to_buffer_offset(inlay_edit.old.end), + new: inlay_snapshot.to_buffer_offset(inlay_edit.new.start) + ..inlay_snapshot.to_buffer_offset(inlay_edit.new.end), + }; old_transforms.seek(&edit.old.start, Bias::Left, &()); if old_transforms.item().map_or(false, |t| t.is_fold()) { edit.old.start = old_transforms.start().0; @@ -580,10 +606,11 @@ impl FoldSnapshot { } } else { let overshoot = InlayPoint(point.0 - cursor.start().0 .0); - FoldPoint(cmp::min( - cursor.start().1 .0 + overshoot, - cursor.end(&()).1 .0, - )) + // TODO kb is this right? + cmp::min( + FoldPoint(cursor.start().1 .0 + overshoot.0), + cursor.end(&()).1, + ) } } @@ -674,6 +701,7 @@ impl FoldSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, + // TODO kb need to call inlay chunks and style them inlay_highlights: Option, ) -> FoldChunks<'a> { let mut highlight_endpoints = Vec::new(); @@ -1355,7 +1383,7 @@ mod tests { let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); { let mut map = FoldMap::new(inlay_snapshot.clone()).0; @@ -1529,8 +1557,9 @@ mod tests { }), }; - let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, buffer_edits); - let (snapshot, edits) = map.read(inlay_snapshot, inlay_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); snapshot_edits.push((snapshot.clone(), edits)); let mut expected_text: String = buffer_snapshot.text().to_string(); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index b9e7119fc99679aaf43c7bb66aef89d7ccb0e006..60aa8ea6eef08bc304cda0b7ffaf9d77054af352 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -282,8 +282,7 @@ impl InlayMap { } else { let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::new(); - // TODO kb something is wrong with how we store it? - let mut transforms = self.transforms; + let transforms = &mut self.transforms; let mut cursor = transforms.cursor::<(usize, InlayOffset)>(); let mut buffer_edits_iter = buffer_edits.iter().peekable(); while let Some(buffer_edit) = buffer_edits_iter.next() { @@ -377,13 +376,14 @@ impl InlayMap { } let new_snapshot = InlaySnapshot { - buffer: buffer_snapshot, + buffer: buffer_snapshot.clone(), transforms: new_transforms, version: post_inc(&mut self.version), }; new_snapshot.check_invariants(); drop(cursor); + // TODO kb remove the 2nd buffer here, leave it in snapshot only? *buffer = buffer_snapshot.clone(); (new_snapshot, inlay_edits.into_inner()) } @@ -434,7 +434,10 @@ impl InlayMap { new: offset..offset, }) .collect(); - self.sync(buffer_snapshot.clone(), buffer_edits) + // TODO kb fugly + let buffer_snapshot_to_sync = buffer_snapshot.clone(); + drop(buffer_snapshot); + self.sync(buffer_snapshot_to_sync, buffer_edits) } #[cfg(any(test, feature = "test-support"))] @@ -567,11 +570,14 @@ impl InlaySnapshot { pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { let mut cursor = self.transforms.cursor::<(Point, InlayOffset)>(); - cursor.seek(&offset, Bias::Left, &()); + // TODO kb is this right? + let buffer_point = self.buffer.offset_to_point(offset); + cursor.seek(&buffer_point, Bias::Left, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { - let overshoot = offset - cursor.start().0; - InlayOffset(cursor.start().1 .0 + overshoot) + let overshoot = buffer_point - cursor.start().0; + let overshoot_offset = self.to_inlay_offset(self.buffer.point_to_offset(overshoot)); + cursor.start().1 + overshoot_offset } Some(Transform::Inlay(_)) => cursor.start().1, None => self.len(), @@ -635,7 +641,7 @@ impl InlaySnapshot { } pub fn text_summary(&self) -> TextSummary { - self.transforms.summary().output + self.transforms.summary().output.clone() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { From 9ae611fa896bfe17e0c6631111b721c0031e2f4e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 12:02:48 +0300 Subject: [PATCH 100/169] Fix InlayMap bugs after the map order revers Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 127 +++++++++------------ 1 file changed, 57 insertions(+), 70 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 60aa8ea6eef08bc304cda0b7ffaf9d77054af352..8bf0226d18883acdadf274c6149df757d33ff54b 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,7 +1,7 @@ use crate::{ inlay_cache::{Inlay, InlayId, InlayProperties}, multi_buffer::{MultiBufferChunks, MultiBufferRows}, - MultiBufferSnapshot, ToPoint, + MultiBufferSnapshot, ToOffset, }; use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; @@ -16,11 +16,9 @@ use text::Patch; use util::post_inc; pub struct InlayMap { - buffer: Mutex, - transforms: SumTree, + snapshot: Mutex, inlays_by_id: HashMap, inlays: Vec, - version: usize, } #[derive(Clone)] @@ -241,11 +239,9 @@ impl InlayMap { ( Self { - buffer: Mutex::new(buffer), - transforms: snapshot.transforms.clone(), + snapshot: Mutex::new(snapshot.clone()), inlays_by_id: Default::default(), inlays: Default::default(), - version, }, snapshot, ) @@ -254,36 +250,40 @@ impl InlayMap { pub fn sync( &mut self, buffer_snapshot: MultiBufferSnapshot, - buffer_edits: Vec>, + mut buffer_edits: Vec>, ) -> (InlaySnapshot, Vec) { - let mut buffer = self.buffer.lock(); + let mut snapshot = self.snapshot.lock(); + + if buffer_edits.is_empty() { + if snapshot.buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + buffer_edits.push(Edit { + old: snapshot.buffer.len()..snapshot.buffer.len(), + new: buffer_snapshot.len()..buffer_snapshot.len(), + }); + } + } + if buffer_edits.is_empty() { - let new_version = if buffer.edit_count() != buffer_snapshot.edit_count() - || buffer.parse_count() != buffer_snapshot.parse_count() - || buffer.diagnostics_update_count() != buffer_snapshot.diagnostics_update_count() - || buffer.git_diff_update_count() != buffer_snapshot.git_diff_update_count() - || buffer.trailing_excerpt_update_count() + if snapshot.buffer.edit_count() != buffer_snapshot.edit_count() + || snapshot.buffer.parse_count() != buffer_snapshot.parse_count() + || snapshot.buffer.diagnostics_update_count() + != buffer_snapshot.diagnostics_update_count() + || snapshot.buffer.git_diff_update_count() + != buffer_snapshot.git_diff_update_count() + || snapshot.buffer.trailing_excerpt_update_count() != buffer_snapshot.trailing_excerpt_update_count() { - post_inc(&mut self.version) - } else { - self.version - }; + snapshot.version += 1; + } - *buffer = buffer_snapshot.clone(); - ( - InlaySnapshot { - buffer: buffer_snapshot, - transforms: SumTree::default(), - version: new_version, - }, - Vec::new(), - ) + snapshot.buffer = buffer_snapshot; + (snapshot.clone(), Vec::new()) } else { let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::new(); - let transforms = &mut self.transforms; - let mut cursor = transforms.cursor::<(usize, InlayOffset)>(); + let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(); let mut buffer_edits_iter = buffer_edits.iter().peekable(); while let Some(buffer_edit) = buffer_edits_iter.next() { new_transforms @@ -311,20 +311,18 @@ impl InlayMap { ); let new_start = InlayOffset(new_transforms.summary().output.len); - let start_point = buffer_edit.new.start.to_point(&buffer_snapshot); let start_ix = match self.inlays.binary_search_by(|probe| { probe .position - .to_point(&buffer_snapshot) - .cmp(&start_point) + .to_offset(&buffer_snapshot) + .cmp(&buffer_edit.new.start) .then(std::cmp::Ordering::Greater) }) { Ok(ix) | Err(ix) => ix, }; for inlay in &self.inlays[start_ix..] { - let buffer_point = inlay.position.to_point(&buffer_snapshot); - let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + let buffer_offset = inlay.position.to_offset(&buffer_snapshot); if buffer_offset > buffer_edit.new.end { break; } @@ -375,17 +373,13 @@ impl InlayMap { new_transforms.push(Transform::Isomorphic(Default::default()), &()); } - let new_snapshot = InlaySnapshot { - buffer: buffer_snapshot.clone(), - transforms: new_transforms, - version: post_inc(&mut self.version), - }; - new_snapshot.check_invariants(); drop(cursor); + snapshot.transforms = new_transforms; + snapshot.version += 1; + snapshot.buffer = buffer_snapshot; + snapshot.check_invariants(); - // TODO kb remove the 2nd buffer here, leave it in snapshot only? - *buffer = buffer_snapshot.clone(); - (new_snapshot, inlay_edits.into_inner()) + (snapshot.clone(), inlay_edits.into_inner()) } } @@ -394,15 +388,14 @@ impl InlayMap { to_remove: Vec, to_insert: Vec<(InlayId, InlayProperties)>, ) -> (InlaySnapshot, Vec) { - let mut buffer_snapshot = self.buffer.lock(); + let snapshot = self.snapshot.lock(); let mut edits = BTreeSet::new(); self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); for inlay_id in to_remove { if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { - let buffer_point = inlay.position.to_point(&buffer_snapshot); - let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); - edits.insert(buffer_offset); + let offset = inlay.position.to_offset(&snapshot.buffer); + edits.insert(offset); } } @@ -415,16 +408,15 @@ impl InlayMap { self.inlays_by_id.insert(inlay.id, inlay.clone()); match self .inlays - .binary_search_by(|probe| probe.position.cmp(&inlay.position, &buffer_snapshot)) + .binary_search_by(|probe| probe.position.cmp(&inlay.position, &snapshot.buffer)) { Ok(ix) | Err(ix) => { self.inlays.insert(ix, inlay.clone()); } } - let buffer_point = inlay.position.to_point(&buffer_snapshot); - let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); - edits.insert(buffer_offset); + let offset = inlay.position.to_offset(&snapshot.buffer); + edits.insert(offset); } let buffer_edits = edits @@ -434,10 +426,9 @@ impl InlayMap { new: offset..offset, }) .collect(); - // TODO kb fugly - let buffer_snapshot_to_sync = buffer_snapshot.clone(); - drop(buffer_snapshot); - self.sync(buffer_snapshot_to_sync, buffer_edits) + let buffer_snapshot = snapshot.buffer.clone(); + drop(snapshot); + self.sync(buffer_snapshot, buffer_edits) } #[cfg(any(test, feature = "test-support"))] @@ -450,10 +441,10 @@ impl InlayMap { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let buffer_snapshot = self.buffer.lock(); + let mut snapshot = self.snapshot.lock(); for _ in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { - let position = buffer_snapshot.random_byte_range(0, rng).start; + let position = snapshot.buffer.random_byte_range(0, rng).start; let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = rng.gen_range(1..=5); let text = util::RandomCharIter::new(&mut *rng) @@ -469,7 +460,7 @@ impl InlayMap { to_insert.push(( InlayId(post_inc(next_inlay_id)), InlayProperties { - position: buffer_snapshot.anchor_at(position, bias), + position: snapshot.buffer.anchor_at(position, bias), text, }, )); @@ -479,7 +470,7 @@ impl InlayMap { } log::info!("removing inlays: {:?}", to_remove); - drop(buffer_snapshot); + drop(snapshot); self.splice(to_remove, to_insert) } } @@ -569,15 +560,12 @@ impl InlaySnapshot { } pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { - let mut cursor = self.transforms.cursor::<(Point, InlayOffset)>(); - // TODO kb is this right? - let buffer_point = self.buffer.offset_to_point(offset); - cursor.seek(&buffer_point, Bias::Left, &()); + let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(); + cursor.seek(&offset, Bias::Left, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { - let overshoot = buffer_point - cursor.start().0; - let overshoot_offset = self.to_inlay_offset(self.buffer.point_to_offset(overshoot)); - cursor.start().1 + overshoot_offset + let overshoot = offset - cursor.start().0; + InlayOffset(cursor.start().1 .0 + overshoot) } Some(Transform::Inlay(_)) => cursor.start().1, None => self.len(), @@ -922,7 +910,7 @@ mod tests { } #[gpui::test] - fn test_buffer_rows(cx: &mut AppContext) { + fn test_inlay_buffer_rows(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); @@ -1018,9 +1006,8 @@ mod tests { .iter() .filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) .map(|inlay| { - let buffer_point = inlay.position.to_point(&buffer_snapshot); - let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); - (buffer_offset, inlay.clone()) + let offset = inlay.position.to_offset(&buffer_snapshot); + (offset, inlay.clone()) }) .collect::>(); let mut expected_text = Rope::from(buffer_snapshot.text().as_str()); From 29bb6c67b05ff1873f9a3fe2a50329992e07c78f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 12:37:25 +0300 Subject: [PATCH 101/169] Fix first FoldMap methods after the map move Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/fold_map.rs | 418 ++++++++++----------- crates/editor/src/display_map/inlay_map.rs | 53 ++- 2 files changed, 232 insertions(+), 239 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 741b79490206b2c7d9bed5f54d14a643205c8082..3a5bfd37e46ac5572b95dc04dba9cb9b70bcb807 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,5 +1,5 @@ use super::{ - inlay_map::{InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + inlay_map::{InlayBufferRows, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, TextHighlights, }; use crate::{ @@ -15,7 +15,7 @@ use std::{ cmp::{self, Ordering}, iter::{self, Peekable}, ops::{Add, AddAssign, Range, Sub}, - sync::atomic::{AtomicUsize, Ordering::SeqCst}, + sync::atomic::Ordering::SeqCst, vec, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; @@ -65,25 +65,17 @@ impl FoldPoint { .transforms .cursor::<(FoldPoint, TransformSummary)>(); cursor.seek(&self, Bias::Right, &()); - let inlay_snapshot = &snapshot.inlay_snapshot; - let to_inlay_offset = |buffer_offset: usize| { - let buffer_point = inlay_snapshot.buffer.offset_to_point(buffer_offset); - inlay_snapshot.to_offset(inlay_snapshot.to_inlay_point(buffer_point)) - }; - let mut inlay_offset = to_inlay_offset(cursor.start().1.output.len); let overshoot = self.0 - cursor.start().1.output.lines; + let mut offset = cursor.start().1.output.len; if !overshoot.is_zero() { let transform = cursor.item().expect("display point out of range"); assert!(transform.output_text.is_none()); - let end_snapshot_offset = snapshot + let end_inlay_offset = snapshot .inlay_snapshot .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); - inlay_offset += end_snapshot_offset - to_inlay_offset(cursor.start().1.input.len); + offset += end_inlay_offset.0 - cursor.start().1.input.len; } - - snapshot - .to_fold_point(inlay_snapshot.to_point(inlay_offset), Bias::Right) - .to_offset(snapshot) + FoldOffset(offset) } } @@ -148,7 +140,7 @@ impl<'a> FoldMapWriter<'a> { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), inlay_snapshot: snapshot, - version: self.0.version.load(SeqCst), + version: self.0.version, ellipses_color: self.0.ellipses_color, }; (snapshot, edits) @@ -201,7 +193,7 @@ impl<'a> FoldMapWriter<'a> { transforms: self.0.transforms.lock().clone(), folds: self.0.folds.clone(), inlay_snapshot: snapshot, - version: self.0.version.load(SeqCst), + version: self.0.version, ellipses_color: self.0.ellipses_color, }; (snapshot, edits) @@ -212,7 +204,7 @@ pub struct FoldMap { inlay_snapshot: Mutex, transforms: Mutex>, folds: SumTree, - version: AtomicUsize, + version: usize, ellipses_color: Option, } @@ -232,14 +224,14 @@ impl FoldMap { &(), )), ellipses_color: None, - version: Default::default(), + version: 0, }; let snapshot = FoldSnapshot { transforms: this.transforms.lock().clone(), folds: this.folds.clone(), inlay_snapshot: inlay_snapshot.clone(), - version: this.version.load(SeqCst), + version: this.version, ellipses_color: None, }; (this, snapshot) @@ -256,7 +248,7 @@ impl FoldMap { transforms: self.transforms.lock().clone(), folds: self.folds.clone(), inlay_snapshot: self.inlay_snapshot.lock().clone(), - version: self.version.load(SeqCst), + version: self.version, ellipses_color: self.ellipses_color, }; (snapshot, edits) @@ -299,108 +291,134 @@ impl FoldMap { } } - fn sync(&self, inlay_snapshot: InlaySnapshot, inlay_edits: Vec) -> Vec { - let buffer = &inlay_snapshot.buffer; - let snapshot = self.inlay_snapshot.lock(); + fn sync( + &mut self, + inlay_snapshot: InlaySnapshot, + inlay_edits: Vec, + ) -> Vec { + if inlay_edits.is_empty() { + if self.inlay_snapshot.lock().version != inlay_snapshot.version { + self.version += 1; + } + *self.inlay_snapshot.lock() = inlay_snapshot; + Vec::new() + } else { + let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); - let mut new_snapshot = snapshot.clone(); - if new_snapshot.version != inlay_snapshot.version { - new_snapshot.version += 1; - } + let mut new_transforms = SumTree::new(); + let mut transforms = self.transforms.lock(); + let mut cursor = transforms.cursor::(); + cursor.seek(&InlayOffset(0), Bias::Right, &()); - let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); + while let Some(mut edit) = inlay_edits_iter.next() { + new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + edit.new.start -= edit.old.start - *cursor.start(); + edit.old.start = *cursor.start(); - let mut new_transforms = SumTree::new(); - let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); - cursor.seek(&0, Bias::Right, &()); + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); - while let Some(inlay_edit) = inlay_edits_iter.next() { - // TODO kb is this right? - let mut edit = Edit { - old: inlay_snapshot.to_buffer_offset(inlay_edit.old.start) - ..inlay_snapshot.to_buffer_offset(inlay_edit.old.end), - new: inlay_snapshot.to_buffer_offset(inlay_edit.new.start) - ..inlay_snapshot.to_buffer_offset(inlay_edit.new.end), - }; - new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - edit.new.start -= edit.old.start - cursor.start(); - edit.old.start = *cursor.start(); + let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize; + loop { + edit.old.end = *cursor.start(); - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); + if let Some(next_edit) = inlay_edits_iter.peek() { + if next_edit.old.start > edit.old.end { + break; + } - let mut delta = edit.new.len() as isize - edit.old.len() as isize; - loop { - edit.old.end = *cursor.start(); + let next_edit = inlay_edits_iter.next().unwrap(); + delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize; - if let Some(next_inlay_edit) = inlay_edits_iter.peek() { - if next_inlay_edit.old.start > inlay_snapshot.to_inlay_offset(edit.old.end) { + if next_edit.old.end >= edit.old.end { + edit.old.end = next_edit.old.end; + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + } + } else { break; } - - let next_inlay_edit = inlay_edits_iter.next().unwrap(); - let next_edit = Edit { - old: inlay_snapshot.to_buffer_offset(next_inlay_edit.old.start) - ..inlay_snapshot.to_buffer_offset(next_inlay_edit.old.end), - new: inlay_snapshot.to_buffer_offset(next_inlay_edit.new.start) - ..inlay_snapshot.to_buffer_offset(next_inlay_edit.new.end), - }; - delta += next_edit.new.len() as isize - next_edit.old.len() as isize; - - if next_edit.old.end >= edit.old.end { - edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); - } - } else { - break; } - } - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; + edit.new.end = + InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); + + let anchor = inlay_snapshot + .buffer + .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let mut folds_cursor = self.folds.cursor::(); + folds_cursor.seek( + &Fold(anchor..Anchor::max()), + Bias::Left, + &inlay_snapshot.buffer, + ); - let anchor = buffer.anchor_before(edit.new.start); - let mut folds_cursor = self.folds.cursor::(); - folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); + let mut folds = iter::from_fn({ + let inlay_snapshot = &inlay_snapshot; + move || { + let item = folds_cursor.item().map(|f| { + let buffer_start = f.0.start.to_offset(&inlay_snapshot.buffer); + let buffer_end = f.0.end.to_offset(&inlay_snapshot.buffer); + inlay_snapshot.to_inlay_offset(buffer_start) + ..inlay_snapshot.to_inlay_offset(buffer_end) + }); + folds_cursor.next(&inlay_snapshot.buffer); + item + } + }) + .peekable(); - let mut folds = iter::from_fn({ - || { - let item = folds_cursor.item().map(|f| { - let fold_buffer_start = f.0.start.to_offset(buffer); - let fold_buffer_end = f.0.end.to_offset(buffer); + while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + let mut fold = folds.next().unwrap(); + let sum = new_transforms.summary(); - inlay_snapshot.to_inlay_offset(fold_buffer_start) - ..inlay_snapshot.to_inlay_offset(fold_buffer_end) - }); - folds_cursor.next(buffer); - item - } - }) - .peekable(); + assert!(fold.start.0 >= sum.input.len); - while folds.peek().map_or(false, |fold| { - inlay_snapshot.to_buffer_offset(fold.start) < edit.new.end - }) { - let mut fold = folds.next().unwrap(); - let sum = new_transforms.summary(); + while folds + .peek() + .map_or(false, |next_fold| next_fold.start <= fold.end) + { + let next_fold = folds.next().unwrap(); + if next_fold.end > fold.end { + fold.end = next_fold.end; + } + } - assert!(inlay_snapshot.to_buffer_offset(fold.start) >= sum.input.len); + if fold.start.0 > sum.input.len { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..fold.start); + new_transforms.push( + Transform { + summary: TransformSummary { + output: text_summary.clone(), + input: text_summary, + }, + output_text: None, + }, + &(), + ); + } - while folds - .peek() - .map_or(false, |next_fold| next_fold.start <= fold.end) - { - let next_fold = folds.next().unwrap(); - if next_fold.end > fold.end { - fold.end = next_fold.end; + if fold.end > fold.start { + let output_text = "⋯"; + new_transforms.push( + Transform { + summary: TransformSummary { + output: TextSummary::from(output_text), + input: inlay_snapshot + .text_summary_for_range(fold.start..fold.end), + }, + output_text: Some(output_text), + }, + &(), + ); } } - if inlay_snapshot.to_buffer_offset(fold.start) > sum.input.len { - let text_summary = buffer.text_summary_for_range::( - sum.input.len..inlay_snapshot.to_buffer_offset(fold.start), - ); + let sum = new_transforms.summary(); + if sum.input.len < edit.new.end.0 { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end); new_transforms.push( Transform { summary: TransformSummary { @@ -412,29 +430,11 @@ impl FoldMap { &(), ); } - - if fold.end > fold.start { - let output_text = "⋯"; - new_transforms.push( - Transform { - summary: TransformSummary { - output: TextSummary::from(output_text), - input: inlay_snapshot.text_summary_for_range( - inlay_snapshot.to_point(fold.start) - ..inlay_snapshot.to_point(fold.end), - ), - }, - output_text: Some(output_text), - }, - &(), - ); - } } - let sum = new_transforms.summary(); - if sum.input.len < edit.new.end { - let text_summary: TextSummary = - buffer.text_summary_for_range(sum.input.len..edit.new.end); + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + let text_summary = inlay_snapshot.text_summary(); new_transforms.push( Transform { summary: TransformSummary { @@ -446,80 +446,59 @@ impl FoldMap { &(), ); } - } - new_transforms.append(cursor.suffix(&()), &()); - if new_transforms.is_empty() { - let text_summary = inlay_snapshot.text_summary(); - new_transforms.push( - Transform { - summary: TransformSummary { - output: text_summary.clone(), - input: text_summary, - }, - output_text: None, - }, - &(), - ); - } + drop(cursor); - drop(cursor); + let mut fold_edits = Vec::with_capacity(inlay_edits.len()); + { + let mut old_transforms = transforms.cursor::<(InlayOffset, FoldOffset)>(); + let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(); - let mut fold_edits = Vec::with_capacity(inlay_edits.len()); - { - let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); - let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); - - for inlay_edit in inlay_edits { - let mut edit = Edit { - old: inlay_snapshot.to_buffer_offset(inlay_edit.old.start) - ..inlay_snapshot.to_buffer_offset(inlay_edit.old.end), - new: inlay_snapshot.to_buffer_offset(inlay_edit.new.start) - ..inlay_snapshot.to_buffer_offset(inlay_edit.new.end), - }; - old_transforms.seek(&edit.old.start, Bias::Left, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - edit.old.start = old_transforms.start().0; - } - let old_start = - old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); + for mut edit in inlay_edits { + old_transforms.seek(&edit.old.start, Bias::Left, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + edit.old.start = old_transforms.start().0; + } + let old_start = + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0).0; - old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); - if old_transforms.item().map_or(false, |t| t.is_fold()) { - old_transforms.next(&()); - edit.old.end = old_transforms.start().0; - } - let old_end = - old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); + old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); + if old_transforms.item().map_or(false, |t| t.is_fold()) { + old_transforms.next(&()); + edit.old.end = old_transforms.start().0; + } + let old_end = + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0).0; - new_transforms.seek(&edit.new.start, Bias::Left, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - edit.new.start = new_transforms.start().0; - } - let new_start = - new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); + new_transforms.seek(&edit.new.start, Bias::Left, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + edit.new.start = new_transforms.start().0; + } + let new_start = + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0).0; - new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); - if new_transforms.item().map_or(false, |t| t.is_fold()) { - new_transforms.next(&()); - edit.new.end = new_transforms.start().0; + new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); + if new_transforms.item().map_or(false, |t| t.is_fold()) { + new_transforms.next(&()); + edit.new.end = new_transforms.start().0; + } + let new_end = + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0).0; + + fold_edits.push(FoldEdit { + old: FoldOffset(old_start)..FoldOffset(old_end), + new: FoldOffset(new_start)..FoldOffset(new_end), + }); } - let new_end = - new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); - fold_edits.push(FoldEdit { - old: FoldOffset(old_start)..FoldOffset(old_end), - new: FoldOffset(new_start)..FoldOffset(new_end), - }); + consolidate_fold_edits(&mut fold_edits); } - consolidate_fold_edits(&mut fold_edits); + *transforms = new_transforms; + *self.inlay_snapshot.lock() = inlay_snapshot; + self.version += 1; + fold_edits } - - *transforms = new_transforms; - *self.inlay_snapshot.lock() = inlay_snapshot; - self.version.fetch_add(1, SeqCst); - fold_edits } } @@ -552,7 +531,7 @@ impl FoldSnapshot { pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&range.start, Bias::Right, &()); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0 .0; @@ -563,12 +542,15 @@ impl FoldSnapshot { [start_in_transform.column as usize..end_in_transform.column as usize], ); } else { - let buffer_start = cursor.start().1 + start_in_transform; - let buffer_end = cursor.start().1 + end_in_transform; + let inlay_start = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary = self .inlay_snapshot - .buffer - .text_summary_for_range(buffer_start..buffer_end); + .text_summary_for_range(inlay_start..inlay_end); } } @@ -582,12 +564,13 @@ impl FoldSnapshot { if let Some(output_text) = transform.output_text { summary += TextSummary::from(&output_text[..end_in_transform.column as usize]); } else { - let buffer_start = cursor.start().1; - let buffer_end = cursor.start().1 + end_in_transform; + let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary += self .inlay_snapshot - .buffer - .text_summary_for_range::(buffer_start..buffer_end); + .text_summary_for_range(inlay_start..inlay_end); } } } @@ -605,12 +588,11 @@ impl FoldSnapshot { cursor.end(&()).1 } } else { - let overshoot = InlayPoint(point.0 - cursor.start().0 .0); - // TODO kb is this right? - cmp::min( - FoldPoint(cursor.start().1 .0 + overshoot.0), - cursor.end(&()).1, - ) + let overshoot = point.0 - cursor.start().0 .0; + FoldPoint(cmp::min( + cursor.start().1 .0 + overshoot, + cursor.end(&()).1 .0, + )) } } @@ -634,12 +616,12 @@ impl FoldSnapshot { } let fold_point = FoldPoint::new(start_row, 0); - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - cursor.start().0 .0; - let buffer_point = cursor.start().1 + overshoot; - let input_buffer_rows = self.inlay_snapshot.buffer.buffer_rows(buffer_point.row); + let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); + let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); FoldBufferRows { fold_point, @@ -1053,8 +1035,8 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { #[derive(Clone)] pub struct FoldBufferRows<'a> { - cursor: Cursor<'a, Transform, (FoldPoint, Point)>, - input_buffer_rows: MultiBufferRows<'a>, + cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>, + input_buffer_rows: InlayBufferRows<'a>, fold_point: FoldPoint, } @@ -1073,7 +1055,7 @@ impl<'a> Iterator for FoldBufferRows<'a> { if self.cursor.item().is_some() { if traversed_fold { - self.input_buffer_rows.seek(self.cursor.start().1.row); + self.input_buffer_rows.seek(self.cursor.start().1.row()); self.input_buffer_rows.next(); } *self.fold_point.row_mut() += 1; @@ -1216,16 +1198,12 @@ impl FoldOffset { .transforms .cursor::<(FoldOffset, TransformSummary)>(); cursor.seek(&self, Bias::Right, &()); - // TODO kb seems wrong to use buffer points? let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) { Point::new(0, (self.0 - cursor.start().0 .0) as u32) } else { - let buffer_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; - let buffer_point = snapshot - .inlay_snapshot - .buffer - .offset_to_point(buffer_offset); - buffer_point - cursor.start().1.input.lines + let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; + let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); + inlay_point.0 - cursor.start().1.input.lines }; FoldPoint(cursor.start().1.output.lines + overshoot) } @@ -1271,18 +1249,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.lines; - } -} - -impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.len; - } -} - pub type FoldEdit = Edit; #[cfg(test)] diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 8bf0226d18883acdadf274c6149df757d33ff54b..69511300bff748c349ad7c0e9108b01b9e355476 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -9,7 +9,7 @@ use language::{Chunk, Edit, Point, Rope, TextSummary}; use parking_lot::Mutex; use std::{ cmp, - ops::{Add, AddAssign, Range, Sub}, + ops::{Add, AddAssign, Range, Sub, SubAssign}, }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; @@ -93,6 +93,12 @@ impl AddAssign for InlayOffset { } } +impl SubAssign for InlayOffset { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += &summary.output.len; @@ -125,6 +131,7 @@ pub struct InlayBufferRows<'a> { transforms: Cursor<'a, Transform, (InlayPoint, Point)>, buffer_rows: MultiBufferRows<'a>, inlay_row: u32, + max_buffer_row: u32, } pub struct InlayChunks<'a> { @@ -193,6 +200,28 @@ impl<'a> Iterator for InlayChunks<'a> { } } +impl<'a> InlayBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + let inlay_point = InlayPoint::new(row, 0); + self.transforms.seek(&inlay_point, Bias::Left, &()); + + let mut buffer_point = self.transforms.start().1; + let buffer_row = if row == 0 { + 0 + } else { + match self.transforms.item() { + Some(Transform::Isomorphic(_)) => { + buffer_point += inlay_point.0 - self.transforms.start().0 .0; + buffer_point.row + } + _ => cmp::min(buffer_point.row + 1, self.max_buffer_row), + } + }; + self.inlay_row = inlay_point.row(); + self.buffer_rows.seek(buffer_row); + } +} + impl<'a> Iterator for InlayBufferRows<'a> { type Item = Option; @@ -632,10 +661,10 @@ impl InlaySnapshot { self.transforms.summary().output.clone() } - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - cursor.start().0 .0; @@ -649,10 +678,8 @@ impl InlaySnapshot { cursor.next(&()); } Some(Transform::Inlay(inlay)) => { - let suffix_start = inlay.text.point_to_offset(overshoot); - let suffix_end = inlay.text.point_to_offset( - cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0, - ); + let suffix_start = overshoot; + let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0; summary = inlay.text.cursor(suffix_start).summary(suffix_end); cursor.next(&()); } @@ -671,10 +698,10 @@ impl InlaySnapshot { let prefix_end = prefix_start + overshoot; summary += self .buffer - .text_summary_for_range::(prefix_start..prefix_end); + .text_summary_for_range::(prefix_start..prefix_end); } Some(Transform::Inlay(inlay)) => { - let prefix_end = inlay.text.point_to_offset(overshoot); + let prefix_end = overshoot; summary += inlay.text.cursor(0).summary::(prefix_end); } None => {} @@ -689,6 +716,7 @@ impl InlaySnapshot { let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left, &()); + let max_buffer_row = self.buffer.max_point().row; let mut buffer_point = cursor.start().1; let buffer_row = if row == 0 { 0 @@ -698,7 +726,7 @@ impl InlaySnapshot { buffer_point += inlay_point.0 - cursor.start().0 .0; buffer_point.row } - _ => cmp::min(buffer_point.row + 1, self.buffer.max_point().row), + _ => cmp::min(buffer_point.row + 1, max_buffer_row), } }; @@ -706,6 +734,7 @@ impl InlaySnapshot { transforms: cursor, inlay_row: inlay_point.row(), buffer_rows: self.buffer.buffer_rows(buffer_row), + max_buffer_row, } } @@ -1049,10 +1078,8 @@ mod tests { start..end ); - let start_point = InlayPoint(expected_text.offset_to_point(start)); - let end_point = InlayPoint(expected_text.offset_to_point(end)); assert_eq!( - inlay_snapshot.text_summary_for_range(start_point..end_point), + inlay_snapshot.text_summary_for_range(start..end), expected_text.slice(start..end).summary() ); } From f2c510000bcb924700ebdfbca689471c3e3d2cdd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 13:20:03 +0300 Subject: [PATCH 102/169] Fix all FoldMap tests (without real inlays inside) Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/block_map.rs | 4 +- crates/editor/src/display_map/fold_map.rs | 288 +++++++++------------ crates/editor/src/display_map/inlay_map.rs | 38 ++- 3 files changed, 159 insertions(+), 171 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index a745fddce78e82b66eff82565736cc6e68bcd080..195962d0c2ab8fdc73431bfbd8029df1af8c9274 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1031,7 +1031,7 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1277,7 +1277,7 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 3a5bfd37e46ac5572b95dc04dba9cb9b70bcb807..fb42e25b01754f3d59833702221236ddedf7b659 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,21 +1,16 @@ use super::{ - inlay_map::{InlayBufferRows, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, TextHighlights, }; -use crate::{ - multi_buffer::{MultiBufferChunks, MultiBufferRows}, - Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset, -}; +use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset}; use collections::BTreeMap; use gpui::{color::Color, fonts::HighlightStyle}; use language::{Chunk, Edit, Point, TextSummary}; -use parking_lot::Mutex; use std::{ any::TypeId, cmp::{self, Ordering}, iter::{self, Peekable}, ops::{Add, AddAssign, Range, Sub}, - sync::atomic::Ordering::SeqCst, vec, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; @@ -51,15 +46,6 @@ impl FoldPoint { InlayPoint(cursor.start().1 .0 + overshoot) } - pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(); - cursor.seek(&self, Bias::Right, &()); - let overshoot = self.0 - cursor.start().0 .0; - snapshot - .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1 .0 + overshoot)) - } - pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { let mut cursor = snapshot .transforms @@ -94,7 +80,7 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let snapshot = self.0.inlay_snapshot.lock().clone(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); for range in ranges.into_iter() { let buffer = &snapshot.buffer; let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer); @@ -123,9 +109,9 @@ impl<'a> FoldMapWriter<'a> { let buffer = &snapshot.buffer; folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, buffer)); - self.0.folds = { + self.0.snapshot.folds = { let mut new_tree = SumTree::new(); - let mut cursor = self.0.folds.cursor::(); + let mut cursor = self.0.snapshot.folds.cursor::(); for fold in folds { new_tree.append(cursor.slice(&fold, Bias::Right, buffer), buffer); new_tree.push(fold, buffer); @@ -136,14 +122,7 @@ impl<'a> FoldMapWriter<'a> { consolidate_inlay_edits(&mut edits); let edits = self.0.sync(snapshot.clone(), edits); - let snapshot = FoldSnapshot { - transforms: self.0.transforms.lock().clone(), - folds: self.0.folds.clone(), - inlay_snapshot: snapshot, - version: self.0.version, - ellipses_color: self.0.ellipses_color, - }; - (snapshot, edits) + (self.0.snapshot.clone(), edits) } pub fn unfold( @@ -153,11 +132,12 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let snapshot = self.0.inlay_snapshot.lock().clone(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); let buffer = &snapshot.buffer; for range in ranges.into_iter() { // Remove intersecting folds and add their ranges to edits that are passed to sync. - let mut folds_cursor = intersecting_folds(&snapshot, &self.0.folds, range, inclusive); + let mut folds_cursor = + intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive); while let Some(fold) = folds_cursor.item() { let offset_range = fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer); if offset_range.end > offset_range.start { @@ -176,8 +156,8 @@ impl<'a> FoldMapWriter<'a> { fold_ixs_to_delete.sort_unstable(); fold_ixs_to_delete.dedup(); - self.0.folds = { - let mut cursor = self.0.folds.cursor::(); + self.0.snapshot.folds = { + let mut cursor = self.0.snapshot.folds.cursor::(); let mut folds = SumTree::new(); for fold_ix in fold_ixs_to_delete { folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer); @@ -189,69 +169,48 @@ impl<'a> FoldMapWriter<'a> { consolidate_inlay_edits(&mut edits); let edits = self.0.sync(snapshot.clone(), edits); - let snapshot = FoldSnapshot { - transforms: self.0.transforms.lock().clone(), - folds: self.0.folds.clone(), - inlay_snapshot: snapshot, - version: self.0.version, - ellipses_color: self.0.ellipses_color, - }; - (snapshot, edits) + (self.0.snapshot.clone(), edits) } } pub struct FoldMap { - inlay_snapshot: Mutex, - transforms: Mutex>, - folds: SumTree, - version: usize, + snapshot: FoldSnapshot, ellipses_color: Option, } impl FoldMap { pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { let this = Self { - inlay_snapshot: Mutex::new(inlay_snapshot.clone()), - folds: Default::default(), - transforms: Mutex::new(SumTree::from_item( - Transform { - summary: TransformSummary { - input: inlay_snapshot.text_summary(), - output: inlay_snapshot.text_summary(), + snapshot: FoldSnapshot { + folds: Default::default(), + transforms: SumTree::from_item( + Transform { + summary: TransformSummary { + input: inlay_snapshot.text_summary(), + output: inlay_snapshot.text_summary(), + }, + output_text: None, }, - output_text: None, - }, - &(), - )), - ellipses_color: None, - version: 0, - }; - - let snapshot = FoldSnapshot { - transforms: this.transforms.lock().clone(), - folds: this.folds.clone(), - inlay_snapshot: inlay_snapshot.clone(), - version: this.version, + &(), + ), + inlay_snapshot: inlay_snapshot.clone(), + version: 0, + ellipses_color: None, + }, ellipses_color: None, }; + let snapshot = this.snapshot.clone(); (this, snapshot) } pub fn read( - &self, + &mut self, inlay_snapshot: InlaySnapshot, edits: Vec, ) -> (FoldSnapshot, Vec) { let edits = self.sync(inlay_snapshot, edits); self.check_invariants(); - let snapshot = FoldSnapshot { - transforms: self.transforms.lock().clone(), - folds: self.folds.clone(), - inlay_snapshot: self.inlay_snapshot.lock().clone(), - version: self.version, - ellipses_color: self.ellipses_color, - }; - (snapshot, edits) + (self.snapshot.clone(), edits) } pub fn write( @@ -274,17 +233,20 @@ impl FoldMap { fn check_invariants(&self) { if cfg!(test) { - let inlay_snapshot = self.inlay_snapshot.lock(); assert_eq!( - self.transforms.lock().summary().input.len, - inlay_snapshot.to_buffer_offset(inlay_snapshot.len()), + self.snapshot.transforms.summary().input.len, + self.snapshot + .inlay_snapshot + .to_buffer_offset(self.snapshot.inlay_snapshot.len()), "transform tree does not match inlay snapshot's length" ); - let mut folds = self.folds.iter().peekable(); + let mut folds = self.snapshot.folds.iter().peekable(); while let Some(fold) = folds.next() { if let Some(next_fold) = folds.peek() { - let comparison = fold.0.cmp(&next_fold.0, &self.inlay_snapshot.lock().buffer); + let comparison = fold + .0 + .cmp(&next_fold.0, &self.snapshot.inlay_snapshot.buffer); assert!(comparison.is_le()); } } @@ -297,17 +259,16 @@ impl FoldMap { inlay_edits: Vec, ) -> Vec { if inlay_edits.is_empty() { - if self.inlay_snapshot.lock().version != inlay_snapshot.version { - self.version += 1; + if self.snapshot.inlay_snapshot.version != inlay_snapshot.version { + self.snapshot.version += 1; } - *self.inlay_snapshot.lock() = inlay_snapshot; + self.snapshot.inlay_snapshot = inlay_snapshot; Vec::new() } else { let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::new(); - let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); + let mut cursor = self.snapshot.transforms.cursor::(); cursor.seek(&InlayOffset(0), Bias::Right, &()); while let Some(mut edit) = inlay_edits_iter.next() { @@ -346,7 +307,7 @@ impl FoldMap { let anchor = inlay_snapshot .buffer .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); - let mut folds_cursor = self.folds.cursor::(); + let mut folds_cursor = self.snapshot.folds.cursor::(); folds_cursor.seek( &Fold(anchor..Anchor::max()), Bias::Left, @@ -451,7 +412,10 @@ impl FoldMap { let mut fold_edits = Vec::with_capacity(inlay_edits.len()); { - let mut old_transforms = transforms.cursor::<(InlayOffset, FoldOffset)>(); + let mut old_transforms = self + .snapshot + .transforms + .cursor::<(InlayOffset, FoldOffset)>(); let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(); for mut edit in inlay_edits { @@ -494,9 +458,9 @@ impl FoldMap { consolidate_fold_edits(&mut fold_edits); } - *transforms = new_transforms; - *self.inlay_snapshot.lock() = inlay_snapshot; - self.version += 1; + self.snapshot.transforms = new_transforms; + self.snapshot.inlay_snapshot = inlay_snapshot; + self.snapshot.version += 1; fold_edits } } @@ -524,10 +488,6 @@ impl FoldSnapshot { self.folds.items(&self.inlay_snapshot.buffer).len() } - pub fn text_summary(&self) -> TextSummary { - self.transforms.summary().output.clone() - } - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); @@ -655,21 +615,24 @@ impl FoldSnapshot { where T: ToOffset, { - let offset = offset.to_offset(&self.inlay_snapshot.buffer); - let mut cursor = self.transforms.cursor::(); - cursor.seek(&offset, Bias::Right, &()); + let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); + let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.output_text.is_some()) } pub fn is_line_folded(&self, buffer_row: u32) -> bool { - let mut cursor = self.transforms.cursor::(); - // TODO kb is this right? - cursor.seek(&Point::new(buffer_row, 0), Bias::Right, &()); + let inlay_point = self + .inlay_snapshot + .to_inlay_point(Point::new(buffer_row, 0)); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_point, Bias::Right, &()); while let Some(transform) = cursor.item() { if transform.output_text.is_some() { return true; } - if cursor.end(&()).row == buffer_row { + if cursor.end(&()).row() == inlay_point.row() { cursor.next(&()) } else { break; @@ -683,39 +646,43 @@ impl FoldSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - // TODO kb need to call inlay chunks and style them inlay_highlights: Option, ) -> FoldChunks<'a> { let mut highlight_endpoints = Vec::new(); - let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); + let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(); - let buffer_end = { + let inlay_end = { transform_cursor.seek(&range.end, Bias::Right, &()); let overshoot = range.end.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + overshoot + transform_cursor.start().1 + InlayOffset(overshoot) }; - let buffer_start = { + let inlay_start = { transform_cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + overshoot + transform_cursor.start().1 + InlayOffset(overshoot) }; if let Some(text_highlights) = text_highlights { if !text_highlights.is_empty() { while transform_cursor.start().0 < range.end { if !transform_cursor.item().unwrap().is_fold() { - let transform_start = self - .inlay_snapshot - .buffer - .anchor_after(cmp::max(buffer_start, transform_cursor.start().1)); + let transform_start = self.inlay_snapshot.buffer.anchor_after( + self.inlay_snapshot.to_buffer_offset(cmp::max( + inlay_start, + transform_cursor.start().1, + )), + ); let transform_end = { - let overshoot = range.end.0 - transform_cursor.start().0 .0; - self.inlay_snapshot.buffer.anchor_before(cmp::min( - transform_cursor.end(&()).1, - transform_cursor.start().1 + overshoot, - )) + let overshoot = + InlayOffset(range.end.0 - transform_cursor.start().0 .0); + self.inlay_snapshot.buffer.anchor_before( + self.inlay_snapshot.to_buffer_offset(cmp::min( + transform_cursor.end(&()).1, + transform_cursor.start().1 + overshoot, + )), + ) }; for (tag, highlights) in text_highlights.iter() { @@ -743,13 +710,17 @@ impl FoldSnapshot { } highlight_endpoints.push(HighlightEndpoint { - offset: range.start.to_offset(&self.inlay_snapshot.buffer), + offset: self.inlay_snapshot.to_inlay_offset( + range.start.to_offset(&self.inlay_snapshot.buffer), + ), is_start: true, tag: *tag, style, }); highlight_endpoints.push(HighlightEndpoint { - offset: range.end.to_offset(&self.inlay_snapshot.buffer), + offset: self.inlay_snapshot.to_inlay_offset( + range.end.to_offset(&self.inlay_snapshot.buffer), + ), is_start: false, tag: *tag, style, @@ -767,12 +738,13 @@ impl FoldSnapshot { FoldChunks { transform_cursor, - buffer_chunks: self - .inlay_snapshot - .buffer - .chunks(buffer_start..buffer_end, language_aware), + inlay_chunks: self.inlay_snapshot.chunks( + inlay_start..inlay_end, + language_aware, + inlay_highlights, + ), inlay_chunk: None, - buffer_offset: buffer_start, + inlay_offset: inlay_start, output_offset: range.start.0, max_output_offset: range.end.0, highlight_endpoints: highlight_endpoints.into_iter().peekable(), @@ -788,33 +760,15 @@ impl FoldSnapshot { #[cfg(test)] pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>(); - cursor.seek(&offset, Bias::Right, &()); - if let Some(transform) = cursor.item() { - let transform_start = cursor.start().0 .0; - if transform.output_text.is_some() { - if offset.0 == transform_start || matches!(bias, Bias::Left) { - FoldOffset(transform_start) - } else { - FoldOffset(cursor.end(&()).0 .0) - } - } else { - let overshoot = offset.0 - transform_start; - let buffer_offset = cursor.start().1 + overshoot; - let clipped_buffer_offset = - self.inlay_snapshot.buffer.clip_offset(buffer_offset, bias); - FoldOffset( - (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) - as usize, - ) - } + if offset > self.len() { + self.len() } else { - FoldOffset(self.transforms.summary().output.len) + self.clip_point(offset.to_point(self), bias).to_offset(self) } } pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { let transform_start = cursor.start().0 .0; @@ -825,11 +779,10 @@ impl FoldSnapshot { FoldPoint(cursor.end(&()).0 .0) } } else { - let overshoot = point.0 - transform_start; - let buffer_position = cursor.start().1 + overshoot; - let clipped_buffer_position = - self.inlay_snapshot.buffer.clip_point(buffer_position, bias); - FoldPoint(cursor.start().0 .0 + (clipped_buffer_position - cursor.start().1)) + let overshoot = InlayPoint(point.0 - transform_start); + let inlay_point = cursor.start().1 + overshoot; + let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); + FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0) } } else { FoldPoint(self.transforms.summary().output.lines) @@ -1067,10 +1020,10 @@ impl<'a> Iterator for FoldBufferRows<'a> { } pub struct FoldChunks<'a> { - transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, - buffer_chunks: MultiBufferChunks<'a>, - inlay_chunk: Option<(usize, Chunk<'a>)>, - buffer_offset: usize, + transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, + inlay_chunks: InlayChunks<'a>, + inlay_chunk: Option<(InlayOffset, Chunk<'a>)>, + inlay_offset: InlayOffset, output_offset: usize, max_output_offset: usize, highlight_endpoints: Peekable>, @@ -1092,10 +1045,10 @@ impl<'a> Iterator for FoldChunks<'a> { // advance the transform and buffer cursors to the end of the fold. if let Some(output_text) = transform.output_text { self.inlay_chunk.take(); - self.buffer_offset += transform.summary.input.len; - self.buffer_chunks.seek(self.buffer_offset); + self.inlay_offset += InlayOffset(transform.summary.input.len); + self.inlay_chunks.seek(self.inlay_offset); - while self.buffer_offset >= self.transform_cursor.end(&()).1 + while self.inlay_offset >= self.transform_cursor.end(&()).1 && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -1112,9 +1065,9 @@ impl<'a> Iterator for FoldChunks<'a> { }); } - let mut next_highlight_endpoint = usize::MAX; + let mut next_highlight_endpoint = InlayOffset(usize::MAX); while let Some(endpoint) = self.highlight_endpoints.peek().copied() { - if endpoint.offset <= self.buffer_offset { + if endpoint.offset <= self.inlay_offset { if endpoint.is_start { self.active_highlights.insert(endpoint.tag, endpoint.style); } else { @@ -1129,20 +1082,20 @@ impl<'a> Iterator for FoldChunks<'a> { // Retrieve a chunk from the current location in the buffer. if self.inlay_chunk.is_none() { - let chunk_offset = self.buffer_chunks.offset(); - self.inlay_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk)); + let chunk_offset = self.inlay_chunks.offset(); + self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk)); } // Otherwise, take a chunk from the buffer's text. if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk { - let buffer_chunk_end = buffer_chunk_start + chunk.text.len(); + let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; let chunk_end = buffer_chunk_end .min(transform_end) .min(next_highlight_endpoint); chunk.text = &chunk.text - [self.buffer_offset - buffer_chunk_start..chunk_end - buffer_chunk_start]; + [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; if !self.active_highlights.is_empty() { let mut highlight_style = HighlightStyle::default(); @@ -1158,7 +1111,7 @@ impl<'a> Iterator for FoldChunks<'a> { self.inlay_chunk.take(); } - self.buffer_offset = chunk_end; + self.inlay_offset = chunk_end; self.output_offset += chunk.text.len(); return Some(chunk); } @@ -1169,7 +1122,7 @@ impl<'a> Iterator for FoldChunks<'a> { #[derive(Copy, Clone, Eq, PartialEq)] struct HighlightEndpoint { - offset: usize, + offset: InlayOffset, is_start: bool, tag: Option, style: HighlightStyle, @@ -1667,6 +1620,7 @@ mod tests { buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right); let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left); let expected_folds = map + .snapshot .folds .items(&buffer_snapshot) .into_iter() @@ -1754,9 +1708,9 @@ mod tests { impl FoldMap { fn merged_fold_ranges(&self) -> Vec> { - let inlay_snapshot = self.inlay_snapshot.lock().clone(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); let buffer = &inlay_snapshot.buffer; - let mut folds = self.folds.items(buffer); + let mut folds = self.snapshot.folds.items(buffer); // Ensure sorting doesn't change how folds get merged and displayed. folds.sort_by(|a, b| a.0.cmp(&b.0, buffer)); let mut fold_ranges = folds @@ -1789,8 +1743,8 @@ mod tests { ) -> Vec<(FoldSnapshot, Vec)> { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=39 if !self.folds.is_empty() => { - let inlay_snapshot = self.inlay_snapshot.lock().clone(); + 0..=39 if !self.snapshot.folds.is_empty() => { + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); let buffer = &inlay_snapshot.buffer; let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { @@ -1805,7 +1759,7 @@ mod tests { snapshot_edits.push((snapshot, edits)); } _ => { - let inlay_snapshot = self.inlay_snapshot.lock().clone(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); let buffer = &inlay_snapshot.buffer; let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 69511300bff748c349ad7c0e9108b01b9e355476..ea6acaaffef59d20717b9811dac7ce43f91f2f9c 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -108,6 +108,22 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct InlayPoint(pub Point); +impl Add for InlayPoint { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for InlayPoint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += &summary.output.lines; @@ -142,6 +158,23 @@ pub struct InlayChunks<'a> { output_offset: InlayOffset, max_output_offset: InlayOffset, highlight_style: Option, + snapshot: &'a InlaySnapshot, +} + +impl<'a> InlayChunks<'a> { + pub fn seek(&mut self, offset: InlayOffset) { + self.transforms.seek(&offset, Bias::Right, &()); + + let buffer_offset = self.snapshot.to_buffer_offset(offset); + self.buffer_chunks.seek(buffer_offset); + self.inlay_chunks = None; + self.buffer_chunk = None; + self.output_offset = offset; + } + + pub fn offset(&self) -> InlayOffset { + self.output_offset + } } impl<'a> Iterator for InlayChunks<'a> { @@ -470,7 +503,7 @@ impl InlayMap { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut snapshot = self.snapshot.lock(); + let snapshot = self.snapshot.lock(); for _ in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { let position = snapshot.buffer.random_byte_range(0, rng).start; @@ -768,6 +801,7 @@ impl InlaySnapshot { output_offset: range.start, max_output_offset: range.end, highlight_style: inlay_highlight_style, + snapshot: self, } } @@ -1079,7 +1113,7 @@ mod tests { ); assert_eq!( - inlay_snapshot.text_summary_for_range(start..end), + inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)), expected_text.slice(start..end).summary() ); } From d4d88252c37103575d920ec22b03982a884e1c4d Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 13:31:53 +0300 Subject: [PATCH 103/169] Fix most of the FoldMap random tests with inlays Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/fold_map.rs | 129 ++++++++++++---------- 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index fb42e25b01754f3d59833702221236ddedf7b659..792bba63d0bb1f2b0dfdf2e9fbb53cf67d2b2339 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -35,6 +35,7 @@ impl FoldPoint { &mut self.0.row } + #[cfg(test)] pub fn column_mut(&mut self) -> &mut u32 { &mut self.0.column } @@ -235,9 +236,7 @@ impl FoldMap { if cfg!(test) { assert_eq!( self.snapshot.transforms.summary().input.len, - self.snapshot - .inlay_snapshot - .to_buffer_offset(self.snapshot.inlay_snapshot.len()), + self.snapshot.inlay_snapshot.len().0, "transform tree does not match inlay snapshot's length" ); @@ -1160,6 +1159,13 @@ impl FoldOffset { }; FoldPoint(cursor.start().1.output.lines + overshoot) } + + pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = self.0 - cursor.start().0 .0; + InlayOffset(cursor.start().1 .0 + overshoot) + } } impl Add for FoldOffset { @@ -1213,6 +1219,7 @@ mod tests { use settings::SettingsStore; use std::{cmp::Reverse, env, mem, sync::Arc}; use sum_tree::TreeMap; + use text::Patch; use util::test::sample_text; use util::RandomCharIter; use Bias::{Left, Right}; @@ -1458,13 +1465,19 @@ mod tests { Arc::new((HighlightStyle::default(), highlight_ranges)), ); + let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); let mut buffer_edits = Vec::new(); + let mut inlay_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=59 => { + 0..=39 => { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } + 40..=59 => { + let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + inlay_edits = edits; + } _ => buffer.update(cx, |buffer, cx| { let subscription = buffer.subscribe(); let edit_count = rng.gen_range(1..=5); @@ -1476,14 +1489,19 @@ mod tests { }), }; - let (inlay_snapshot, inlay_edits) = + let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + let inlay_edits = Patch::new(inlay_edits) + .compose(new_inlay_edits) + .into_inner(); let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); snapshot_edits.push((snapshot.clone(), edits)); - let mut expected_text: String = buffer_snapshot.text().to_string(); + let mut expected_text: String = inlay_snapshot.text().to_string(); for fold_range in map.merged_fold_ranges().into_iter().rev() { - expected_text.replace_range(fold_range.start..fold_range.end, "⋯"); + let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start); + let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end); + expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); } assert_eq!(snapshot.text(), expected_text); @@ -1493,27 +1511,27 @@ mod tests { expected_text.matches('\n').count() + 1 ); - let mut prev_row = 0; - let mut expected_buffer_rows = Vec::new(); - for fold_range in map.merged_fold_ranges().into_iter() { - let fold_start = buffer_snapshot.offset_to_point(fold_range.start).row; - let fold_end = buffer_snapshot.offset_to_point(fold_range.end).row; - expected_buffer_rows.extend( - buffer_snapshot - .buffer_rows(prev_row) - .take((1 + fold_start - prev_row) as usize), - ); - prev_row = 1 + fold_end; - } - expected_buffer_rows.extend(buffer_snapshot.buffer_rows(prev_row)); - - assert_eq!( - expected_buffer_rows.len(), - expected_text.matches('\n').count() + 1, - "wrong expected buffer rows {:?}. text: {:?}", - expected_buffer_rows, - expected_text - ); + // let mut prev_row = 0; + // let mut expected_buffer_rows = Vec::new(); + // for fold_range in map.merged_fold_ranges().into_iter() { + // let fold_start = buffer_snapshot.offset_to_point(fold_range.start).row; + // let fold_end = buffer_snapshot.offset_to_point(fold_range.end).row; + // expected_buffer_rows.extend( + // buffer_snapshot + // .buffer_rows(prev_row) + // .take((1 + fold_start - prev_row) as usize), + // ); + // prev_row = 1 + fold_end; + // } + // expected_buffer_rows.extend(buffer_snapshot.buffer_rows(prev_row)); + + // assert_eq!( + // expected_buffer_rows.len(), + // expected_text.matches('\n').count() + 1, + // "wrong expected buffer rows {:?}. text: {:?}", + // expected_buffer_rows, + // expected_text + // ); for (output_row, line) in expected_text.lines().enumerate() { let line_len = snapshot.line_len(output_row as u32); @@ -1532,18 +1550,17 @@ mod tests { let mut char_column = 0; for c in expected_text.chars() { let inlay_point = fold_point.to_inlay_point(&snapshot); - let buffer_point = inlay_snapshot.to_buffer_point(inlay_point); - let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + let inlay_offset = fold_offset.to_inlay_offset(&snapshot); assert_eq!( snapshot.to_fold_point(inlay_point, Right), fold_point, "{:?} -> fold point", - buffer_point, + inlay_point, ); assert_eq!( - inlay_snapshot.to_buffer_offset(inlay_snapshot.to_offset(inlay_point)), - buffer_offset, - "inlay_snapshot.to_buffer_offset(inlay_snapshot.to_offset(({:?}))", + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "inlay_snapshot.to_offset({:?})", inlay_point, ); assert_eq!( @@ -1592,28 +1609,28 @@ mod tests { ); } - let mut fold_row = 0; - while fold_row < expected_buffer_rows.len() as u32 { - fold_row = snapshot - .clip_point(FoldPoint::new(fold_row, 0), Bias::Right) - .row(); - assert_eq!( - snapshot.buffer_rows(fold_row).collect::>(), - expected_buffer_rows[(fold_row as usize)..], - "wrong buffer rows starting at fold row {}", - fold_row, - ); - fold_row += 1; - } - - let fold_start_rows = map - .merged_fold_ranges() - .iter() - .map(|range| range.start.to_point(&buffer_snapshot).row) - .collect::>(); - for row in fold_start_rows { - assert!(snapshot.is_line_folded(row)); - } + // let mut fold_row = 0; + // while fold_row < expected_buffer_rows.len() as u32 { + // fold_row = snapshot + // .clip_point(FoldPoint::new(fold_row, 0), Bias::Right) + // .row(); + // assert_eq!( + // snapshot.buffer_rows(fold_row).collect::>(), + // expected_buffer_rows[(fold_row as usize)..], + // "wrong buffer rows starting at fold row {}", + // fold_row, + // ); + // fold_row += 1; + // } + + // let fold_start_rows = map + // .merged_fold_ranges() + // .iter() + // .map(|range| range.start.to_point(&buffer_snapshot).row) + // .collect::>(); + // for row in fold_start_rows { + // assert!(snapshot.is_line_folded(row)); + // } for _ in 0..5 { let end = From 2b989a9f1259b483fc312412d6cf50d072cc9418 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 15:06:55 +0300 Subject: [PATCH 104/169] Fix all the tests Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/fold_map.rs | 130 +++++++++++++--------- 1 file changed, 80 insertions(+), 50 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 792bba63d0bb1f2b0dfdf2e9fbb53cf67d2b2339..7e39402e59c28a391cfa9ea5484fa9a9c3ae2ed1 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -622,22 +622,31 @@ impl FoldSnapshot { } pub fn is_line_folded(&self, buffer_row: u32) -> bool { - let inlay_point = self + let mut inlay_point = self .inlay_snapshot .to_inlay_point(Point::new(buffer_row, 0)); let mut cursor = self.transforms.cursor::(); cursor.seek(&inlay_point, Bias::Right, &()); - while let Some(transform) = cursor.item() { - if transform.output_text.is_some() { - return true; + loop { + match cursor.item() { + Some(transform) => { + let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point); + if buffer_point.row != buffer_row { + return false; + } else if transform.output_text.is_some() { + return true; + } + } + None => return false, } + if cursor.end(&()).row() == inlay_point.row() { - cursor.next(&()) + cursor.next(&()); } else { - break; + inlay_point.0 += Point::new(1, 0); + cursor.seek(&inlay_point, Bias::Right, &()); } } - false } pub fn chunks<'a>( @@ -1491,6 +1500,8 @@ mod tests { let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + log::info!("inlay text {:?}", inlay_snapshot.text()); + let inlay_edits = Patch::new(inlay_edits) .compose(new_inlay_edits) .into_inner(); @@ -1511,27 +1522,31 @@ mod tests { expected_text.matches('\n').count() + 1 ); - // let mut prev_row = 0; - // let mut expected_buffer_rows = Vec::new(); - // for fold_range in map.merged_fold_ranges().into_iter() { - // let fold_start = buffer_snapshot.offset_to_point(fold_range.start).row; - // let fold_end = buffer_snapshot.offset_to_point(fold_range.end).row; - // expected_buffer_rows.extend( - // buffer_snapshot - // .buffer_rows(prev_row) - // .take((1 + fold_start - prev_row) as usize), - // ); - // prev_row = 1 + fold_end; - // } - // expected_buffer_rows.extend(buffer_snapshot.buffer_rows(prev_row)); - - // assert_eq!( - // expected_buffer_rows.len(), - // expected_text.matches('\n').count() + 1, - // "wrong expected buffer rows {:?}. text: {:?}", - // expected_buffer_rows, - // expected_text - // ); + let mut prev_row = 0; + let mut expected_buffer_rows = Vec::new(); + for fold_range in map.merged_fold_ranges().into_iter() { + let fold_start = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.start)) + .row(); + let fold_end = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.end)) + .row(); + expected_buffer_rows.extend( + inlay_snapshot + .buffer_rows(prev_row) + .take((1 + fold_start - prev_row) as usize), + ); + prev_row = 1 + fold_end; + } + expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row)); + + assert_eq!( + expected_buffer_rows.len(), + expected_text.matches('\n').count() + 1, + "wrong expected buffer rows {:?}. text: {:?}", + expected_buffer_rows, + expected_text + ); for (output_row, line) in expected_text.lines().enumerate() { let line_len = snapshot.line_len(output_row as u32); @@ -1609,28 +1624,43 @@ mod tests { ); } - // let mut fold_row = 0; - // while fold_row < expected_buffer_rows.len() as u32 { - // fold_row = snapshot - // .clip_point(FoldPoint::new(fold_row, 0), Bias::Right) - // .row(); - // assert_eq!( - // snapshot.buffer_rows(fold_row).collect::>(), - // expected_buffer_rows[(fold_row as usize)..], - // "wrong buffer rows starting at fold row {}", - // fold_row, - // ); - // fold_row += 1; - // } - - // let fold_start_rows = map - // .merged_fold_ranges() - // .iter() - // .map(|range| range.start.to_point(&buffer_snapshot).row) - // .collect::>(); - // for row in fold_start_rows { - // assert!(snapshot.is_line_folded(row)); - // } + let mut fold_row = 0; + while fold_row < expected_buffer_rows.len() as u32 { + assert_eq!( + snapshot.buffer_rows(fold_row).collect::>(), + expected_buffer_rows[(fold_row as usize)..], + "wrong buffer rows starting at fold row {}", + fold_row, + ); + fold_row += 1; + } + + let folded_buffer_rows = map + .merged_fold_ranges() + .iter() + .flat_map(|range| { + let start_row = range.start.to_point(&buffer_snapshot).row; + let end = range.end.to_point(&buffer_snapshot); + if end.column == 0 { + start_row..end.row + } else { + start_row..end.row + 1 + } + }) + .collect::>(); + for row in 0..=buffer_snapshot.max_point().row { + assert_eq!( + snapshot.is_line_folded(row), + folded_buffer_rows.contains(&row), + "expected buffer row {}{} to be folded", + row, + if folded_buffer_rows.contains(&row) { + "" + } else { + " not" + } + ); + } for _ in 0..5 { let end = From 76d35b71220431c36bab0be128a533d02048145e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 15 Jun 2023 21:18:09 +0300 Subject: [PATCH 105/169] Use proper, limited excerpt ranges and manage inlay cache properly --- crates/editor/src/display_map.rs | 4 + crates/editor/src/editor.rs | 146 +++++++++++++++++++++++-------- crates/editor/src/inlay_cache.rs | 53 +++++++---- crates/editor/src/scroll.rs | 7 +- 4 files changed, 157 insertions(+), 53 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cddc10fba7d4c74ba8ff649abb324a8b04f80766..73be4763199808e69508114535a9acb941efdd26 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -249,6 +249,10 @@ impl DisplayMap { to_insert: Vec<(InlayId, InlayProperties)>, cx: &mut ModelContext, ) { + if to_remove.is_empty() && to_insert.is_empty() { + return; + } + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 10c86ceb29c3a534876582e9bddccb49a5d7f064..bf6e88de50cc7258c249e27a6b7b0a26a89c95ce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -55,7 +55,7 @@ use gpui::{ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; use inlay_cache::{ - Inlay, InlayCache, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, QueryInlaysRange, + Inlay, InlayCache, InlayFetchRange, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, }; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; @@ -1302,7 +1302,7 @@ impl Editor { } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(InlayRefreshReason::Regular, cx); + editor.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); }; })); } @@ -1357,7 +1357,6 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - // TODO kb has to live between editor reopens inlay_cache: InlayCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ @@ -1383,7 +1382,7 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::Regular, cx); + this.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); this } @@ -2606,36 +2605,35 @@ impl Editor { return; } + let multi_buffer_handle = self.buffer().clone(); match reason { - InlayRefreshReason::Settings(new_settings) => { + InlayRefreshReason::SettingsChange(new_settings) => { let InlaySplice { to_remove, to_insert, } = self.inlay_cache.apply_settings(new_settings); self.splice_inlay_hints(to_remove, to_insert, cx); } - InlayRefreshReason::Regular => { - let buffer_handle = self.buffer().clone(); - let inlay_fetch_ranges = buffer_handle - .read(cx) - .snapshot(cx) - .excerpts() - .filter_map(|(excerpt_id, buffer_snapshot, excerpt_range)| { - let buffer_path = buffer_snapshot.resolve_file_path(cx, true)?; - let buffer_id = buffer_snapshot.remote_id(); - let buffer_version = buffer_snapshot.version().clone(); - let max_buffer_offset = buffer_snapshot.len(); - let excerpt_range = excerpt_range.context; - Some(QueryInlaysRange { - buffer_path, - buffer_id, - buffer_version, - excerpt_id, - excerpt_offset_range: excerpt_range.start.offset - ..excerpt_range.end.offset.min(max_buffer_offset), - }) + InlayRefreshReason::Scroll(scrolled_to) => { + let ranges_to_add = self + .excerpt_visible_offsets(&multi_buffer_handle, cx) + .into_iter() + .find_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { + let buffer_id = scrolled_to.anchor.buffer_id?; + if buffer_id == buffer.read(cx).remote_id() + && scrolled_to.anchor.excerpt_id == excerpt_id + { + get_inlay_fetch_range( + &buffer, + excerpt_id, + excerpt_visible_offset_range, + cx, + ) + } else { + None + } }) - .collect::>(); + .into_iter(); cx.spawn(|editor, mut cx| async move { let InlaySplice { @@ -2643,9 +2641,36 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.fetch_inlays( - buffer_handle, - inlay_fetch_ranges.into_iter(), + editor + .inlay_cache + .append_inlays(multi_buffer_handle, ranges_to_add, cx) + })? + .await + .context("inlay cache hint fetch")?; + + editor.update(&mut cx, |editor, cx| { + editor.splice_inlay_hints(to_remove, to_insert, cx) + }) + }) + .detach_and_log_err(cx); + } + InlayRefreshReason::OpenExcerptsChange => { + let new_ranges = self + .excerpt_visible_offsets(&multi_buffer_handle, cx) + .into_iter() + .filter_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { + get_inlay_fetch_range(&buffer, excerpt_id, excerpt_visible_offset_range, cx) + }) + .collect::>(); + cx.spawn(|editor, mut cx| async move { + let InlaySplice { + to_remove, + to_insert, + } = editor + .update(&mut cx, |editor, cx| { + editor.inlay_cache.replace_inlays( + multi_buffer_handle, + new_ranges.into_iter(), cx, ) })? @@ -2656,9 +2681,32 @@ impl Editor { editor.splice_inlay_hints(to_remove, to_insert, cx) }) }) + // TODO kb needs cancellation for many excerpts cases like `project search "test"` .detach_and_log_err(cx); } - } + }; + } + + fn excerpt_visible_offsets( + &self, + multi_buffer: &ModelHandle, + cx: &mut ViewContext<'_, '_, Editor>, + ) -> Vec<(ModelHandle, Range, ExcerptId)> { + let multi_buffer = multi_buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let multi_buffer_visible_start = self + .scroll_manager + .anchor() + .anchor + .to_point(&multi_buffer_snapshot); + let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( + multi_buffer_visible_start + + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0), + Bias::Left, + ); + let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; + + multi_buffer.range_to_buffer_ranges(multi_buffer_visible_range, cx) } fn splice_inlay_hints( @@ -7245,7 +7293,7 @@ impl Editor { event: &multi_buffer::Event, cx: &mut ViewContext, ) { - let refresh_inlay_hints = match event { + let refresh_inlays = match event { multi_buffer::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); @@ -7293,7 +7341,7 @@ impl Editor { } multi_buffer::Event::DiffBaseChanged => { cx.emit(Event::DiffBaseChanged); - true + false } multi_buffer::Event::Closed => { cx.emit(Event::Closed); @@ -7306,8 +7354,8 @@ impl Editor { _ => false, }; - if refresh_inlay_hints { - self.refresh_inlays(InlayRefreshReason::Regular, cx); + if refresh_inlays { + self.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); } } @@ -7318,7 +7366,7 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); self.refresh_inlays( - InlayRefreshReason::Settings(settings::get::(cx).inlay_hints), + InlayRefreshReason::SettingsChange(settings::get::(cx).inlay_hints), cx, ); } @@ -7612,6 +7660,34 @@ impl Editor { } } +fn get_inlay_fetch_range( + buffer: &ModelHandle, + excerpt_id: ExcerptId, + excerpt_visible_offset_range: Range, + cx: &mut ViewContext<'_, '_, Editor>, +) -> Option { + let buffer = buffer.read(cx); + let buffer_snapshot = buffer.snapshot(); + let max_buffer_len = buffer.len(); + let visible_offset_range_len = excerpt_visible_offset_range.len(); + + let query_range_start = excerpt_visible_offset_range + .start + .saturating_sub(visible_offset_range_len); + let query_range_end = max_buffer_len.min( + excerpt_visible_offset_range + .end + .saturating_add(visible_offset_range_len), + ); + Some(InlayFetchRange { + buffer_path: buffer_snapshot.resolve_file_path(cx, true)?, + buffer_id: buffer.remote_id(), + buffer_version: buffer.version().clone(), + excerpt_id, + excerpt_offset_query_range: query_range_start..query_range_end, + }) +} + fn consume_contiguous_rows( contiguous_row_selections: &mut Vec>, selection: &Selection, diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index 6cb38958793c1da7ddd63404f6d6386adc4d20e4..659473f5e61a416d049baaa1eb0ae3f20e35e339 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::{editor_settings, Anchor, Editor, ExcerptId, MultiBuffer}; +use crate::{editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, MultiBuffer}; use anyhow::Context; use clock::{Global, Local}; use gpui::{ModelHandle, Task, ViewContext}; @@ -29,8 +29,9 @@ pub struct InlayProperties { #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { - Settings(editor_settings::InlayHints), - Regular, + SettingsChange(editor_settings::InlayHints), + Scroll(ScrollAnchor), + OpenExcerptsChange, } #[derive(Debug, Clone, Default)] @@ -88,12 +89,12 @@ pub struct InlaySplice { pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } -pub struct QueryInlaysRange { +pub struct InlayFetchRange { pub buffer_id: u64, pub buffer_path: PathBuf, pub buffer_version: Global, pub excerpt_id: ExcerptId, - pub excerpt_offset_range: Range, + pub excerpt_offset_query_range: Range, } impl InlayCache { @@ -105,10 +106,29 @@ impl InlayCache { } } - pub fn fetch_inlays( + pub fn append_inlays( &mut self, multi_buffer: ModelHandle, - inlay_fetch_ranges: impl Iterator, + ranges_to_add: impl Iterator, + cx: &mut ViewContext, + ) -> Task> { + self.fetch_inlays(multi_buffer, ranges_to_add, false, cx) + } + + pub fn replace_inlays( + &mut self, + multi_buffer: ModelHandle, + new_ranges: impl Iterator, + cx: &mut ViewContext, + ) -> Task> { + self.fetch_inlays(multi_buffer, new_ranges, true, cx) + } + + fn fetch_inlays( + &mut self, + multi_buffer: ModelHandle, + inlay_fetch_ranges: impl Iterator, + replace_old: bool, cx: &mut ViewContext, ) -> Task> { let mut inlay_fetch_tasks = Vec::new(); @@ -127,13 +147,11 @@ impl InlayCache { else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; let task = editor .update(&mut cx, |editor, cx| { - let max_buffer_offset = buffer_handle.read(cx).len(); - let excerpt_offset_range = &inlay_fetch_range.excerpt_offset_range; editor.project.as_ref().map(|project| { project.update(cx, |project, cx| { project.query_inlay_hints_for_buffer( buffer_handle, - excerpt_offset_range.start..excerpt_offset_range.end.min(max_buffer_offset), + inlay_fetch_range.excerpt_offset_query_range.clone(), cx, ) }) @@ -163,16 +181,17 @@ impl InlayCache { for task_result in futures::future::join_all(inlay_fetch_tasks).await { match task_result { - Ok((request_key, response_inlays)) => { + Ok((inlay_fetch_range, response_inlays)) => { + // TODO kb different caching now let inlays_per_excerpt = HashMap::from_iter([( - request_key.excerpt_id, + inlay_fetch_range.excerpt_id, response_inlays .map(|excerpt_inlays| { excerpt_inlays.into_iter().fold( OrderedByAnchorOffset::default(), |mut ordered_inlays, inlay| { let anchor = multi_buffer_snapshot.anchor_in_excerpt( - request_key.excerpt_id, + inlay_fetch_range.excerpt_id, inlay.position, ); ordered_inlays.add(anchor, inlay); @@ -180,14 +199,16 @@ impl InlayCache { }, ) }) - .map(|inlays| (request_key.excerpt_offset_range, inlays)), + .map(|inlays| { + (inlay_fetch_range.excerpt_offset_query_range, inlays) + }), )]); - match inlay_updates.entry(request_key.buffer_path) { + match inlay_updates.entry(inlay_fetch_range.buffer_path) { hash_map::Entry::Occupied(mut o) => { o.get_mut().1.extend(inlays_per_excerpt); } hash_map::Entry::Vacant(v) => { - v.insert((request_key.buffer_version, inlays_per_excerpt)); + v.insert((inlay_fetch_range.buffer_version, inlays_per_excerpt)); } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 09b75bc6802acd37bec4afc88a3e22b65593041d..e0ca2a1ebf534fc5c2e26ebdbc33c21cea8d52ed 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -18,6 +18,7 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, + inlay_cache::InlayRefreshReason, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -176,7 +177,7 @@ impl ScrollManager { autoscroll: bool, workspace_id: Option, cx: &mut ViewContext, - ) { + ) -> ScrollAnchor { let (new_anchor, top_row) = if scroll_position.y() <= 0. { ( ScrollAnchor { @@ -205,6 +206,7 @@ impl ScrollManager { }; self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx); + new_anchor } fn set_anchor( @@ -312,7 +314,7 @@ impl Editor { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - self.scroll_manager.set_scroll_position( + let scroll_anchor = self.scroll_manager.set_scroll_position( scroll_position, &map, local, @@ -320,6 +322,7 @@ impl Editor { workspace_id, cx, ); + self.refresh_inlays(InlayRefreshReason::Scroll(scroll_anchor), cx); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { From e217a95fcc12f30b9862b7fafbca59f7c2cede96 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 17:07:04 +0300 Subject: [PATCH 106/169] Cleanup the warnings --- crates/editor/src/display_map/fold_map.rs | 1 + crates/editor/src/display_map/inlay_map.rs | 13 ++------ crates/editor/src/editor.rs | 39 ++++++++++------------ crates/editor/src/inlay_cache.rs | 10 +++--- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 7e39402e59c28a391cfa9ea5484fa9a9c3ae2ed1..fe3c0d86abd18661b1be39ec6b1a7bd36b5b5fb0 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1169,6 +1169,7 @@ impl FoldOffset { FoldPoint(cursor.start().1.output.lines + overshoot) } + #[cfg(test)] pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); cursor.seek(&self, Bias::Right, &()); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index ea6acaaffef59d20717b9811dac7ce43f91f2f9c..d731e0dc4c4f297720607ee370d3cafd6454cf5c 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -13,7 +13,6 @@ use std::{ }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; -use util::post_inc; pub struct InlayMap { snapshot: Mutex, @@ -284,10 +283,6 @@ impl InlayPoint { pub fn row(self) -> u32 { self.0.row } - - pub fn column(self) -> u32 { - self.0.column - } } impl InlayMap { @@ -493,13 +488,14 @@ impl InlayMap { self.sync(buffer_snapshot, buffer_edits) } - #[cfg(any(test, feature = "test-support"))] + #[cfg(test)] pub(crate) fn randomly_mutate( &mut self, next_inlay_id: &mut usize, rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec) { use rand::prelude::*; + use util::post_inc; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); @@ -590,11 +586,6 @@ impl InlaySnapshot { } } - pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator { - self.chunks(self.to_offset(start)..self.len(), false, None) - .flat_map(|chunk| chunk.text.chars()) - } - pub fn to_buffer_point(&self, point: InlayPoint) -> Point { let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); cursor.seek(&point, Bias::Right, &()); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bf6e88de50cc7258c249e27a6b7b0a26a89c95ce..1e5370cce50be74960d53060633e93c057df57a5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -55,7 +55,7 @@ use gpui::{ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; use inlay_cache::{ - Inlay, InlayCache, InlayFetchRange, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, + Inlay, InlayCache, InlayHintQuery, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, }; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; @@ -1302,7 +1302,7 @@ impl Editor { } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); + editor.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); }; })); } @@ -1382,7 +1382,7 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); + this.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); this } @@ -2615,7 +2615,7 @@ impl Editor { self.splice_inlay_hints(to_remove, to_insert, cx); } InlayRefreshReason::Scroll(scrolled_to) => { - let ranges_to_add = self + let addition_queries = self .excerpt_visible_offsets(&multi_buffer_handle, cx) .into_iter() .find_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { @@ -2623,12 +2623,7 @@ impl Editor { if buffer_id == buffer.read(cx).remote_id() && scrolled_to.anchor.excerpt_id == excerpt_id { - get_inlay_fetch_range( - &buffer, - excerpt_id, - excerpt_visible_offset_range, - cx, - ) + inlay_hint_query(&buffer, excerpt_id, excerpt_visible_offset_range, cx) } else { None } @@ -2641,9 +2636,11 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor - .inlay_cache - .append_inlays(multi_buffer_handle, ranges_to_add, cx) + editor.inlay_cache.append_inlays( + multi_buffer_handle, + addition_queries, + cx, + ) })? .await .context("inlay cache hint fetch")?; @@ -2654,12 +2651,12 @@ impl Editor { }) .detach_and_log_err(cx); } - InlayRefreshReason::OpenExcerptsChange => { - let new_ranges = self + InlayRefreshReason::VisibleExcerptsChange => { + let replacement_queries = self .excerpt_visible_offsets(&multi_buffer_handle, cx) .into_iter() .filter_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { - get_inlay_fetch_range(&buffer, excerpt_id, excerpt_visible_offset_range, cx) + inlay_hint_query(&buffer, excerpt_id, excerpt_visible_offset_range, cx) }) .collect::>(); cx.spawn(|editor, mut cx| async move { @@ -2670,7 +2667,7 @@ impl Editor { .update(&mut cx, |editor, cx| { editor.inlay_cache.replace_inlays( multi_buffer_handle, - new_ranges.into_iter(), + replacement_queries.into_iter(), cx, ) })? @@ -7355,7 +7352,7 @@ impl Editor { }; if refresh_inlays { - self.refresh_inlays(InlayRefreshReason::OpenExcerptsChange, cx); + self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); } } @@ -7660,12 +7657,12 @@ impl Editor { } } -fn get_inlay_fetch_range( +fn inlay_hint_query( buffer: &ModelHandle, excerpt_id: ExcerptId, excerpt_visible_offset_range: Range, cx: &mut ViewContext<'_, '_, Editor>, -) -> Option { +) -> Option { let buffer = buffer.read(cx); let buffer_snapshot = buffer.snapshot(); let max_buffer_len = buffer.len(); @@ -7679,7 +7676,7 @@ fn get_inlay_fetch_range( .end .saturating_add(visible_offset_range_len), ); - Some(InlayFetchRange { + Some(InlayHintQuery { buffer_path: buffer_snapshot.resolve_file_path(cx, true)?, buffer_id: buffer.remote_id(), buffer_version: buffer.version().clone(), diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index 659473f5e61a416d049baaa1eb0ae3f20e35e339..b571a29cc14c6458ebf45f3bfec1c6455fbd1fff 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -31,7 +31,7 @@ pub struct InlayProperties { pub enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), Scroll(ScrollAnchor), - OpenExcerptsChange, + VisibleExcerptsChange, } #[derive(Debug, Clone, Default)] @@ -89,7 +89,7 @@ pub struct InlaySplice { pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } -pub struct InlayFetchRange { +pub struct InlayHintQuery { pub buffer_id: u64, pub buffer_path: PathBuf, pub buffer_version: Global, @@ -109,7 +109,7 @@ impl InlayCache { pub fn append_inlays( &mut self, multi_buffer: ModelHandle, - ranges_to_add: impl Iterator, + ranges_to_add: impl Iterator, cx: &mut ViewContext, ) -> Task> { self.fetch_inlays(multi_buffer, ranges_to_add, false, cx) @@ -118,7 +118,7 @@ impl InlayCache { pub fn replace_inlays( &mut self, multi_buffer: ModelHandle, - new_ranges: impl Iterator, + new_ranges: impl Iterator, cx: &mut ViewContext, ) -> Task> { self.fetch_inlays(multi_buffer, new_ranges, true, cx) @@ -127,7 +127,7 @@ impl InlayCache { fn fetch_inlays( &mut self, multi_buffer: ModelHandle, - inlay_fetch_ranges: impl Iterator, + inlay_fetch_ranges: impl Iterator, replace_old: bool, cx: &mut ViewContext, ) -> Task> { From 49c00fd57117e3d0042f3ac2713e707152a49081 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 17:56:44 +0300 Subject: [PATCH 107/169] Generate InlayIds in InlayMap, prepare InlayCache for refactoring --- crates/editor/src/display_map.rs | 9 +- crates/editor/src/display_map/fold_map.rs | 3 +- crates/editor/src/display_map/inlay_map.rs | 116 ++-- crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/display_map/wrap_map.rs | 4 +- crates/editor/src/editor.rs | 32 +- crates/editor/src/inlay_cache.rs | 690 ++++++++++----------- 7 files changed, 391 insertions(+), 465 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 73be4763199808e69508114535a9acb941efdd26..b0b97fd70d49fe64f3bf4b5f70c4de2d06614d15 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -246,11 +246,11 @@ impl DisplayMap { pub fn splice_inlays>( &mut self, to_remove: Vec, - to_insert: Vec<(InlayId, InlayProperties)>, + to_insert: Vec>, cx: &mut ModelContext, - ) { + ) -> Vec { if to_remove.is_empty() && to_insert.is_empty() { - return; + return Vec::new(); } let buffer_snapshot = self.buffer.read(cx).snapshot(cx); @@ -264,13 +264,14 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); + let (snapshot, edits, new_inlay_ids) = self.inlay_map.splice(to_remove, to_insert); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); + new_inlay_ids } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index fe3c0d86abd18661b1be39ec6b1a7bd36b5b5fb0..fb0892b82d92152c8732cdfb8fcd8ef1c651d654 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1475,7 +1475,6 @@ mod tests { Arc::new((HighlightStyle::default(), highlight_ranges)), ); - let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); let mut buffer_edits = Vec::new(); @@ -1485,7 +1484,7 @@ mod tests { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } 40..=59 => { - let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + let (_, edits) = inlay_map.randomly_mutate(&mut rng); inlay_edits = edits; } _ => buffer.update(cx, |buffer, cx| { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index d731e0dc4c4f297720607ee370d3cafd6454cf5c..3571a603a9c8f485626b0ae73bc8d75c521dcd98 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -13,11 +13,13 @@ use std::{ }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; +use util::post_inc; pub struct InlayMap { snapshot: Mutex, inlays_by_id: HashMap, inlays: Vec, + next_inlay_id: usize, } #[derive(Clone)] @@ -297,8 +299,9 @@ impl InlayMap { ( Self { snapshot: Mutex::new(snapshot.clone()), - inlays_by_id: Default::default(), - inlays: Default::default(), + inlays_by_id: HashMap::default(), + inlays: Vec::new(), + next_inlay_id: 0, }, snapshot, ) @@ -443,8 +446,8 @@ impl InlayMap { pub fn splice>( &mut self, to_remove: Vec, - to_insert: Vec<(InlayId, InlayProperties)>, - ) -> (InlaySnapshot, Vec) { + to_insert: Vec>, + ) -> (InlaySnapshot, Vec, Vec) { let snapshot = self.snapshot.lock(); let mut edits = BTreeSet::new(); @@ -456,12 +459,14 @@ impl InlayMap { } } - for (id, properties) in to_insert { + let mut new_inlay_ids = Vec::with_capacity(to_insert.len()); + for properties in to_insert { let inlay = Inlay { - id, + id: InlayId(post_inc(&mut self.next_inlay_id)), position: properties.position, text: properties.text.into(), }; + new_inlay_ids.push(inlay.id); self.inlays_by_id.insert(inlay.id, inlay.clone()); match self .inlays @@ -485,17 +490,16 @@ impl InlayMap { .collect(); let buffer_snapshot = snapshot.buffer.clone(); drop(snapshot); - self.sync(buffer_snapshot, buffer_edits) + let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits); + (snapshot, edits, new_inlay_ids) } #[cfg(test)] pub(crate) fn randomly_mutate( &mut self, - next_inlay_id: &mut usize, rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec) { use rand::prelude::*; - use util::post_inc; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); @@ -515,13 +519,10 @@ impl InlayMap { bias, text ); - to_insert.push(( - InlayId(post_inc(next_inlay_id)), - InlayProperties { - position: snapshot.buffer.anchor_at(position, bias), - text, - }, - )); + to_insert.push(InlayProperties { + position: snapshot.buffer.anchor_at(position, bias), + text, + }); } else { to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap()); } @@ -529,7 +530,8 @@ impl InlayMap { log::info!("removing inlays: {:?}", to_remove); drop(snapshot); - self.splice(to_remove, to_insert) + let (snapshot, edits, _) = self.splice(to_remove, to_insert); + (snapshot, edits) } } @@ -840,7 +842,6 @@ mod tests { use settings::SettingsStore; use std::env; use text::Patch; - use util::post_inc; #[gpui::test] fn test_basic_inlays(cx: &mut AppContext) { @@ -848,17 +849,13 @@ mod tests { let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abcdefghi"); - let mut next_inlay_id = 0; - let (inlay_snapshot, _) = inlay_map.splice( + let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), - vec![( - InlayId(post_inc(&mut next_inlay_id)), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|123|", - }, - )], + vec![InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|123|", + }], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( @@ -928,23 +925,17 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); - let (inlay_snapshot, _) = inlay_map.splice( + let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), vec![ - ( - InlayId(post_inc(&mut next_inlay_id)), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(3), - text: "|123|", - }, - ), - ( - InlayId(post_inc(&mut next_inlay_id)), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|456|", - }, - ), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|", + }, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|", + }, ], ); assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); @@ -958,7 +949,7 @@ mod tests { assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. - let (inlay_snapshot, _) = inlay_map + let (inlay_snapshot, _, _) = inlay_map .splice::(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } @@ -969,30 +960,21 @@ mod tests { let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); - let (inlay_snapshot, _) = inlay_map.splice( + let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), vec![ - ( - InlayId(0), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(0), - text: "|123|\n", - }, - ), - ( - InlayId(1), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(4), - text: "|456|", - }, - ), - ( - InlayId(1), - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(7), - text: "\n|567|\n", - }, - ), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(0), + text: "|123|\n", + }, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(4), + text: "|456|", + }, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(7), + text: "\n|567|\n", + }, ], ); assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); @@ -1023,8 +1005,6 @@ mod tests { log::info!("buffer text: {:?}", buffer_snapshot.text()); let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let mut next_inlay_id = 0; - for _ in 0..operations { let mut inlay_edits = Patch::default(); @@ -1032,7 +1012,7 @@ mod tests { let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=50 => { - let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + let (snapshot, edits) = inlay_map.randomly_mutate(&mut rng); log::info!("mutated text: {:?}", snapshot.text()); inlay_edits = Patch::new(edits); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 9157caace496e72597e70ac039e7797672122dbc..021712cd4041fc620d68f0cd8ed332567f236ae3 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -708,7 +708,7 @@ mod tests { fold_map.randomly_mutate(&mut rng); let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); + let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 5197a2e0de49150f38e37e39ca7e42ba4050c026..a1f60920cd2b2068ec4ae08eb295b0b7a5ff8eb7 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1119,7 +1119,6 @@ mod tests { ); log::info!("Wrapped text: {:?}", actual_text); - let mut next_inlay_id = 0; let mut edits = Vec::new(); for _i in 0..operations { log::info!("{} ==============================================", _i); @@ -1147,8 +1146,7 @@ mod tests { } } 40..=59 => { - let (inlay_snapshot, inlay_edits) = - inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + let (inlay_snapshot, inlay_edits) = inlay_map.randomly_mutate(&mut rng); let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1e5370cce50be74960d53060633e93c057df57a5..4a40d6ef2f2885e980adfddb1d9ac9ee91046836 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2709,13 +2709,13 @@ impl Editor { fn splice_inlay_hints( &self, to_remove: Vec, - to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, + to_insert: Vec<(Anchor, project::InlayHint)>, cx: &mut ViewContext, ) { let buffer = self.buffer.read(cx).read(cx); - let new_inlays: Vec<(InlayId, InlayProperties)> = to_insert + let new_inlays = to_insert .into_iter() - .map(|(inlay_id, hint_anchor, hint)| { + .map(|(hint_anchor, hint)| { let mut text = hint.text(); // TODO kb styling instead? if hint.padding_right { @@ -2725,13 +2725,10 @@ impl Editor { text.insert(0, ' '); } - ( - inlay_id, - InlayProperties { - position: hint_anchor.bias_left(&buffer), - text, - }, - ) + InlayProperties { + position: hint_anchor.bias_left(&buffer), + text, + } }) .collect(); drop(buffer); @@ -3485,15 +3482,10 @@ impl Editor { to_remove.push(suggestion.id); } - let to_insert = vec![( - // TODO kb check how can I get the unique id for the suggestion - // Move the generation of the id inside the map - InlayId(usize::MAX), - InlayProperties { - position: cursor, - text: text.clone(), - }, - )]; + let to_insert = vec![InlayProperties { + position: cursor, + text: text.clone(), + }]; self.display_map.update(cx, move |map, cx| { map.splice_inlays(to_remove, to_insert, cx) }); @@ -7664,7 +7656,6 @@ fn inlay_hint_query( cx: &mut ViewContext<'_, '_, Editor>, ) -> Option { let buffer = buffer.read(cx); - let buffer_snapshot = buffer.snapshot(); let max_buffer_len = buffer.len(); let visible_offset_range_len = excerpt_visible_offset_range.len(); @@ -7677,7 +7668,6 @@ fn inlay_hint_query( .saturating_add(visible_offset_range_len), ); Some(InlayHintQuery { - buffer_path: buffer_snapshot.resolve_file_path(cx, true)?, buffer_id: buffer.remote_id(), buffer_version: buffer.version().clone(), excerpt_id, diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index b571a29cc14c6458ebf45f3bfec1c6455fbd1fff..7a362085147d514f7f71ea0a00e1308fec81b505 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -1,19 +1,13 @@ -use std::{ - cmp, - ops::Range, - path::{Path, PathBuf}, -}; +use std::ops::Range; use crate::{editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, MultiBuffer}; -use anyhow::Context; -use clock::{Global, Local}; +use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; -use log::error; use project::{InlayHint, InlayHintKind}; -use util::post_inc; -use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use collections::{HashMap, HashSet}; +// TODO kb move to inlay_map along with the next one? #[derive(Debug, Clone)] pub struct Inlay { pub id: InlayId, @@ -36,48 +30,14 @@ pub enum InlayRefreshReason { #[derive(Debug, Clone, Default)] pub struct InlayCache { - inlays_per_buffer: HashMap, + inlays_per_buffer: HashMap, allowed_hint_kinds: HashSet>, - next_inlay_id: usize, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AnchorKey { - offset: usize, - version: Local, -} - -#[derive(Clone, Debug)] -pub struct OrderedByAnchorOffset(pub BTreeMap); - -impl OrderedByAnchorOffset { - pub fn add(&mut self, anchor: Anchor, t: T) { - let key = AnchorKey { - offset: anchor.text_anchor.offset, - version: anchor.text_anchor.timestamp, - }; - self.0.insert(key, (anchor, t)); - } - - fn into_ordered_elements(self) -> impl Iterator { - self.0.into_values() - } - - fn ordered_elements(&self) -> impl Iterator { - self.0.values() - } -} - -impl Default for OrderedByAnchorOffset { - fn default() -> Self { - Self(BTreeMap::default()) - } } #[derive(Clone, Debug, Default)] struct BufferInlays { buffer_version: Global, - inlays_per_excerpts: HashMap>, + ordered_by_anchor_inlays: Vec<(Anchor, InlayId, InlayHint)>, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -86,12 +46,11 @@ pub struct InlayId(pub usize); #[derive(Debug, Default)] pub struct InlaySplice { pub to_remove: Vec, - pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, + pub to_insert: Vec<(Anchor, InlayHint)>, } pub struct InlayHintQuery { pub buffer_id: u64, - pub buffer_path: PathBuf, pub buffer_version: Global, pub excerpt_id: ExcerptId, pub excerpt_offset_query_range: Range, @@ -100,307 +59,8 @@ pub struct InlayHintQuery { impl InlayCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { - inlays_per_buffer: HashMap::default(), allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), - next_inlay_id: 0, - } - } - - pub fn append_inlays( - &mut self, - multi_buffer: ModelHandle, - ranges_to_add: impl Iterator, - cx: &mut ViewContext, - ) -> Task> { - self.fetch_inlays(multi_buffer, ranges_to_add, false, cx) - } - - pub fn replace_inlays( - &mut self, - multi_buffer: ModelHandle, - new_ranges: impl Iterator, - cx: &mut ViewContext, - ) -> Task> { - self.fetch_inlays(multi_buffer, new_ranges, true, cx) - } - - fn fetch_inlays( - &mut self, - multi_buffer: ModelHandle, - inlay_fetch_ranges: impl Iterator, - replace_old: bool, - cx: &mut ViewContext, - ) -> Task> { - let mut inlay_fetch_tasks = Vec::new(); - for inlay_fetch_range in inlay_fetch_ranges { - let inlays_up_to_date = self.inlays_up_to_date( - &inlay_fetch_range.buffer_path, - &inlay_fetch_range.buffer_version, - inlay_fetch_range.excerpt_id, - ); - let task_multi_buffer = multi_buffer.clone(); - let task = cx.spawn(|editor, mut cx| async move { - if inlays_up_to_date { - anyhow::Ok((inlay_fetch_range, None)) - } else { - let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id)) - else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; - let task = editor - .update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - project.query_inlay_hints_for_buffer( - buffer_handle, - inlay_fetch_range.excerpt_offset_query_range.clone(), - cx, - ) - }) - }) - }) - .context("inlays fecth task spawn")?; - - Ok((inlay_fetch_range, match task { - Some(task) => task.await.context("inlays for buffer task")?, - None => Some(Vec::new()), - })) - } - }); - inlay_fetch_tasks.push(task); - } - - let final_task = cx.spawn(|editor, mut cx| async move { - let mut inlay_updates: HashMap< - PathBuf, - ( - Global, - HashMap, OrderedByAnchorOffset)>>, - ), - > = HashMap::default(); - let multi_buffer_snapshot = - editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; - - for task_result in futures::future::join_all(inlay_fetch_tasks).await { - match task_result { - Ok((inlay_fetch_range, response_inlays)) => { - // TODO kb different caching now - let inlays_per_excerpt = HashMap::from_iter([( - inlay_fetch_range.excerpt_id, - response_inlays - .map(|excerpt_inlays| { - excerpt_inlays.into_iter().fold( - OrderedByAnchorOffset::default(), - |mut ordered_inlays, inlay| { - let anchor = multi_buffer_snapshot.anchor_in_excerpt( - inlay_fetch_range.excerpt_id, - inlay.position, - ); - ordered_inlays.add(anchor, inlay); - ordered_inlays - }, - ) - }) - .map(|inlays| { - (inlay_fetch_range.excerpt_offset_query_range, inlays) - }), - )]); - match inlay_updates.entry(inlay_fetch_range.buffer_path) { - hash_map::Entry::Occupied(mut o) => { - o.get_mut().1.extend(inlays_per_excerpt); - } - hash_map::Entry::Vacant(v) => { - v.insert((inlay_fetch_range.buffer_version, inlays_per_excerpt)); - } - } - } - Err(e) => error!("Failed to update inlays for buffer: {e:#}"), - } - } - - let updates = if !inlay_updates.is_empty() { - let inlays_update = editor.update(&mut cx, |editor, _| { - editor.inlay_cache.apply_fetch_inlays(inlay_updates) - })?; - inlays_update - } else { - InlaySplice::default() - }; - - anyhow::Ok(updates) - }); - - final_task - } - - fn inlays_up_to_date( - &self, - buffer_path: &Path, - buffer_version: &Global, - excerpt_id: ExcerptId, - ) -> bool { - let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; - let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version - || buffer_inlays.buffer_version.changed_since(&buffer_version); - buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) - } - - fn apply_fetch_inlays( - &mut self, - fetched_inlays: HashMap< - PathBuf, - ( - Global, - HashMap, OrderedByAnchorOffset)>>, - ), - >, - ) -> InlaySplice { - let mut old_inlays = self.inlays_per_buffer.clone(); - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - - for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays { - match old_inlays.remove(&buffer_path) { - Some(mut old_buffer_inlays) => { - for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { - let (_, mut new_excerpt_inlays) = match new_excerpt_inlays { - Some((excerpt_offset_range, new_inlays)) => ( - excerpt_offset_range, - new_inlays.into_ordered_elements().fuse().peekable(), - ), - None => continue, - }; - if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { - continue; - } - - let self_inlays_per_buffer = self - .inlays_per_buffer - .get_mut(&buffer_path) - .expect("element expected: `old_inlays.remove` returned `Some`"); - - if old_buffer_inlays - .inlays_per_excerpts - .remove(&excerpt_id) - .is_some() - { - let self_excerpt_inlays = self_inlays_per_buffer - .inlays_per_excerpts - .get_mut(&excerpt_id) - .expect("element expected: `old_excerpt_inlays` is `Some`"); - let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); - // TODO kb update inner buffer_id and version with the new data? - self_excerpt_inlays.0.retain( - |_, (old_anchor, (old_inlay_id, old_inlay))| { - let mut retain = false; - - while let Some(new_offset) = new_excerpt_inlays - .peek() - .map(|(new_anchor, _)| new_anchor.text_anchor.offset) - { - let old_offset = old_anchor.text_anchor.offset; - match new_offset.cmp(&old_offset) { - cmp::Ordering::Less => { - let (new_anchor, new_inlay) = - new_excerpt_inlays.next().expect( - "element expected: `peek` returned `Some`", - ); - hints_to_add.push(( - new_anchor, - ( - InlayId(post_inc(&mut self.next_inlay_id)), - new_inlay, - ), - )); - } - cmp::Ordering::Equal => { - let (new_anchor, new_inlay) = - new_excerpt_inlays.next().expect( - "element expected: `peek` returned `Some`", - ); - if &new_inlay == old_inlay { - retain = true; - } else { - hints_to_add.push(( - new_anchor, - ( - InlayId(post_inc( - &mut self.next_inlay_id, - )), - new_inlay, - ), - )); - } - } - cmp::Ordering::Greater => break, - } - } - - if !retain { - to_remove.push(*old_inlay_id); - } - retain - }, - ); - - for (new_anchor, (id, new_inlay)) in hints_to_add { - self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone())); - to_insert.push((id, new_anchor, new_inlay)); - } - } - - for (new_anchor, new_inlay) in new_excerpt_inlays { - let id = InlayId(post_inc(&mut self.next_inlay_id)); - self_inlays_per_buffer - .inlays_per_excerpts - .entry(excerpt_id) - .or_default() - .add(new_anchor, (id, new_inlay.clone())); - to_insert.push((id, new_anchor, new_inlay)); - } - } - } - None => { - let mut inlays_per_excerpts: HashMap< - ExcerptId, - OrderedByAnchorOffset<(InlayId, InlayHint)>, - > = HashMap::default(); - for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { - if let Some((_, new_ordered_inlays)) = new_ordered_inlays { - for (new_anchor, new_inlay) in - new_ordered_inlays.into_ordered_elements() - { - let id = InlayId(post_inc(&mut self.next_inlay_id)); - inlays_per_excerpts - .entry(new_excerpt_id) - .or_default() - .add(new_anchor, (id, new_inlay.clone())); - to_insert.push((id, new_anchor, new_inlay)); - } - } - } - self.inlays_per_buffer.insert( - buffer_path, - BufferInlays { - buffer_version, - inlays_per_excerpts, - }, - ); - } - } - } - - for (_, old_buffer_inlays) in old_inlays { - for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts { - for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() { - to_remove.push(id_to_remove); - } - } - } - - to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); - - InlaySplice { - to_remove, - to_insert, + inlays_per_buffer: HashMap::default(), } } @@ -420,22 +80,17 @@ impl InlayCache { .collect::>(); let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - for (anchor, (inlay_id, inlay_hint)) in self + for (_, inlay_id, inlay_hint) in self .inlays_per_buffer .iter() - .map(|(_, buffer_inlays)| { - buffer_inlays - .inlays_per_excerpts - .iter() - .map(|(_, excerpt_inlays)| excerpt_inlays.ordered_elements()) - .flatten() - }) + .map(|(_, buffer_inlays)| buffer_inlays.ordered_by_anchor_inlays.iter()) .flatten() { if removed_hint_kinds.contains(&inlay_hint.kind) { to_remove.push(*inlay_id); } else if new_allowed_hint_kinds.contains(&inlay_hint.kind) { - to_insert.push((*inlay_id, *anchor, inlay_hint.to_owned())); + todo!("TODO kb: agree with InlayMap how splice works") + // to_insert.push((*inlay_id, *anchor, inlay_hint.to_owned())); } } @@ -450,20 +105,323 @@ impl InlayCache { pub fn clear(&mut self) -> Vec { self.inlays_per_buffer .drain() - .map(|(_, buffer_inlays)| { + .flat_map(|(_, buffer_inlays)| { buffer_inlays - .inlays_per_excerpts + .ordered_by_anchor_inlays .into_iter() - .map(|(_, excerpt_inlays)| { - excerpt_inlays - .into_ordered_elements() - .map(|(_, (id, _))| id) - }) - .flatten() + .map(|(_, id, _)| id) }) - .flatten() .collect() } + + pub fn append_inlays( + &mut self, + multi_buffer: ModelHandle, + ranges_to_add: impl Iterator, + cx: &mut ViewContext, + ) -> Task> { + self.fetch_inlays(multi_buffer, ranges_to_add, false, cx) + } + + pub fn replace_inlays( + &mut self, + multi_buffer: ModelHandle, + new_ranges: impl Iterator, + cx: &mut ViewContext, + ) -> Task> { + self.fetch_inlays(multi_buffer, new_ranges, true, cx) + } + + fn fetch_inlays( + &mut self, + multi_buffer: ModelHandle, + inlay_fetch_ranges: impl Iterator, + replace_old: bool, + cx: &mut ViewContext, + ) -> Task> { + // TODO kb + todo!("TODO kb") + } + + // fn fetch_inlays( + // &mut self, + // multi_buffer: ModelHandle, + // inlay_fetch_ranges: impl Iterator, + // replace_old: bool, + // cx: &mut ViewContext, + // ) -> Task> { + // let mut inlay_fetch_tasks = Vec::new(); + // for inlay_fetch_range in inlay_fetch_ranges { + // let inlays_up_to_date = self.inlays_up_to_date( + // &inlay_fetch_range.buffer_path, + // &inlay_fetch_range.buffer_version, + // inlay_fetch_range.excerpt_id, + // ); + // let task_multi_buffer = multi_buffer.clone(); + // let task = cx.spawn(|editor, mut cx| async move { + // if inlays_up_to_date { + // anyhow::Ok((inlay_fetch_range, None)) + // } else { + // let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id)) + // else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; + // let task = editor + // .update(&mut cx, |editor, cx| { + // editor.project.as_ref().map(|project| { + // project.update(cx, |project, cx| { + // project.query_inlay_hints_for_buffer( + // buffer_handle, + // inlay_fetch_range.excerpt_offset_query_range.clone(), + // cx, + // ) + // }) + // }) + // }) + // .context("inlays fecth task spawn")?; + + // Ok((inlay_fetch_range, match task { + // Some(task) => task.await.context("inlays for buffer task")?, + // None => Some(Vec::new()), + // })) + // } + // }); + // inlay_fetch_tasks.push(task); + // } + + // let final_task = cx.spawn(|editor, mut cx| async move { + // let mut inlay_updates: HashMap< + // PathBuf, + // ( + // Global, + // HashMap, OrderedByAnchorOffset)>>, + // ), + // > = HashMap::default(); + // let multi_buffer_snapshot = + // editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; + + // for task_result in futures::future::join_all(inlay_fetch_tasks).await { + // match task_result { + // Ok((inlay_fetch_range, response_inlays)) => { + // // TODO kb different caching now + // let inlays_per_excerpt = HashMap::from_iter([( + // inlay_fetch_range.excerpt_id, + // response_inlays + // .map(|excerpt_inlays| { + // excerpt_inlays.into_iter().fold( + // OrderedByAnchorOffset::default(), + // |mut ordered_inlays, inlay| { + // let anchor = multi_buffer_snapshot.anchor_in_excerpt( + // inlay_fetch_range.excerpt_id, + // inlay.position, + // ); + // ordered_inlays.add(anchor, inlay); + // ordered_inlays + // }, + // ) + // }) + // .map(|inlays| { + // (inlay_fetch_range.excerpt_offset_query_range, inlays) + // }), + // )]); + // match inlay_updates.entry(inlay_fetch_range.buffer_path) { + // hash_map::Entry::Occupied(mut o) => { + // o.get_mut().1.extend(inlays_per_excerpt); + // } + // hash_map::Entry::Vacant(v) => { + // v.insert((inlay_fetch_range.buffer_version, inlays_per_excerpt)); + // } + // } + // } + // Err(e) => error!("Failed to update inlays for buffer: {e:#}"), + // } + // } + + // let updates = if !inlay_updates.is_empty() { + // let inlays_update = editor.update(&mut cx, |editor, _| { + // editor.inlay_cache.apply_fetch_inlays(inlay_updates) + // })?; + // inlays_update + // } else { + // InlaySplice::default() + // }; + + // anyhow::Ok(updates) + // }); + + // final_task + // } + + // fn inlays_up_to_date( + // &self, + // buffer_path: &Path, + // buffer_version: &Global, + // excerpt_id: ExcerptId, + // ) -> bool { + // let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; + // let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version + // || buffer_inlays.buffer_version.changed_since(&buffer_version); + // buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) + // } + + // fn apply_fetch_inlays( + // &mut self, + // fetched_inlays: HashMap< + // PathBuf, + // ( + // Global, + // HashMap, OrderedByAnchorOffset)>>, + // ), + // >, + // ) -> InlaySplice { + // let mut old_inlays = self.inlays_per_buffer.clone(); + // let mut to_remove = Vec::new(); + // let mut to_insert = Vec::new(); + + // for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays { + // match old_inlays.remove(&buffer_path) { + // Some(mut old_buffer_inlays) => { + // for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { + // let (_, mut new_excerpt_inlays) = match new_excerpt_inlays { + // Some((excerpt_offset_range, new_inlays)) => ( + // excerpt_offset_range, + // new_inlays.into_ordered_elements().fuse().peekable(), + // ), + // None => continue, + // }; + // if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { + // continue; + // } + + // let self_inlays_per_buffer = self + // .inlays_per_buffer + // .get_mut(&buffer_path) + // .expect("element expected: `old_inlays.remove` returned `Some`"); + + // if old_buffer_inlays + // .inlays_per_excerpts + // .remove(&excerpt_id) + // .is_some() + // { + // let self_excerpt_inlays = self_inlays_per_buffer + // .inlays_per_excerpts + // .get_mut(&excerpt_id) + // .expect("element expected: `old_excerpt_inlays` is `Some`"); + // let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); + // // TODO kb update inner buffer_id and version with the new data? + // self_excerpt_inlays.0.retain( + // |_, (old_anchor, (old_inlay_id, old_inlay))| { + // let mut retain = false; + + // while let Some(new_offset) = new_excerpt_inlays + // .peek() + // .map(|(new_anchor, _)| new_anchor.text_anchor.offset) + // { + // let old_offset = old_anchor.text_anchor.offset; + // match new_offset.cmp(&old_offset) { + // cmp::Ordering::Less => { + // let (new_anchor, new_inlay) = + // new_excerpt_inlays.next().expect( + // "element expected: `peek` returned `Some`", + // ); + // hints_to_add.push(( + // new_anchor, + // ( + // InlayId(post_inc(&mut self.next_inlay_id)), + // new_inlay, + // ), + // )); + // } + // cmp::Ordering::Equal => { + // let (new_anchor, new_inlay) = + // new_excerpt_inlays.next().expect( + // "element expected: `peek` returned `Some`", + // ); + // if &new_inlay == old_inlay { + // retain = true; + // } else { + // hints_to_add.push(( + // new_anchor, + // ( + // InlayId(post_inc( + // &mut self.next_inlay_id, + // )), + // new_inlay, + // ), + // )); + // } + // } + // cmp::Ordering::Greater => break, + // } + // } + + // if !retain { + // to_remove.push(*old_inlay_id); + // } + // retain + // }, + // ); + + // for (new_anchor, (id, new_inlay)) in hints_to_add { + // self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone())); + // to_insert.push((id, new_anchor, new_inlay)); + // } + // } + + // for (new_anchor, new_inlay) in new_excerpt_inlays { + // let id = InlayId(post_inc(&mut self.next_inlay_id)); + // self_inlays_per_buffer + // .inlays_per_excerpts + // .entry(excerpt_id) + // .or_default() + // .add(new_anchor, (id, new_inlay.clone())); + // to_insert.push((id, new_anchor, new_inlay)); + // } + // } + // } + // None => { + // let mut inlays_per_excerpts: HashMap< + // ExcerptId, + // OrderedByAnchorOffset<(InlayId, InlayHint)>, + // > = HashMap::default(); + // for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { + // if let Some((_, new_ordered_inlays)) = new_ordered_inlays { + // for (new_anchor, new_inlay) in + // new_ordered_inlays.into_ordered_elements() + // { + // let id = InlayId(post_inc(&mut self.next_inlay_id)); + // inlays_per_excerpts + // .entry(new_excerpt_id) + // .or_default() + // .add(new_anchor, (id, new_inlay.clone())); + // to_insert.push((id, new_anchor, new_inlay)); + // } + // } + // } + // self.inlays_per_buffer.insert( + // buffer_path, + // BufferInlays { + // buffer_version, + // inlays_per_excerpts, + // }, + // ); + // } + // } + // } + + // for (_, old_buffer_inlays) in old_inlays { + // for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts { + // for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() { + // to_remove.push(id_to_remove); + // } + // } + // } + + // to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); + + // InlaySplice { + // to_remove, + // to_insert, + // } + // } } fn allowed_inlay_hint_types( From 8f68688a6465bdf22f7b1daccd2b75700dc59257 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 16 Jun 2023 23:59:22 +0300 Subject: [PATCH 108/169] Allow readding inlays with existing ids, move inlay types --- crates/editor/src/display_map.rs | 13 +- crates/editor/src/display_map/inlay_map.rs | 110 +++++++++++------ crates/editor/src/editor.rs | 135 ++++++++++++--------- crates/editor/src/inlay_cache.rs | 129 ++++++++++---------- crates/editor/src/multi_buffer.rs | 16 ++- crates/project/src/project.rs | 5 +- 6 files changed, 248 insertions(+), 160 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index b0b97fd70d49fe64f3bf4b5f70c4de2d06614d15..791665bf78374f1d4d5c874e49c123836bfde424 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,10 +4,7 @@ mod inlay_map; mod tab_map; mod wrap_map; -use crate::{ - inlay_cache::{InlayId, InlayProperties}, - Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, -}; +use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::FoldMap; @@ -31,6 +28,8 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; +pub use self::inlay_map::{Inlay, InlayId, InlayProperties}; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -243,10 +242,14 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } + pub fn current_inlays(&self) -> impl Iterator { + self.inlay_map.current_inlays() + } + pub fn splice_inlays>( &mut self, to_remove: Vec, - to_insert: Vec>, + to_insert: Vec<(Option, InlayProperties)>, cx: &mut ModelContext, ) -> Vec { if to_remove.is_empty() && to_insert.is_empty() { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 3571a603a9c8f485626b0ae73bc8d75c521dcd98..0c7d1bd60396fb95a6e2989bb7fbd23c2d7c080f 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,7 +1,6 @@ use crate::{ - inlay_cache::{Inlay, InlayId, InlayProperties}, multi_buffer::{MultiBufferChunks, MultiBufferRows}, - MultiBufferSnapshot, ToOffset, + Anchor, MultiBufferSnapshot, ToOffset, }; use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; @@ -35,6 +34,22 @@ enum Transform { Inlay(Inlay), } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InlayId(usize); + +#[derive(Debug, Clone)] +pub struct Inlay { + pub id: InlayId, + pub position: Anchor, + pub text: text::Rope, +} + +#[derive(Debug, Clone)] +pub struct InlayProperties { + pub position: Anchor, + pub text: T, +} + impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -446,7 +461,7 @@ impl InlayMap { pub fn splice>( &mut self, to_remove: Vec, - to_insert: Vec>, + to_insert: Vec<(Option, InlayProperties)>, ) -> (InlaySnapshot, Vec, Vec) { let snapshot = self.snapshot.lock(); let mut edits = BTreeSet::new(); @@ -460,13 +475,15 @@ impl InlayMap { } let mut new_inlay_ids = Vec::with_capacity(to_insert.len()); - for properties in to_insert { + for (existing_id, properties) in to_insert { let inlay = Inlay { - id: InlayId(post_inc(&mut self.next_inlay_id)), + id: existing_id.unwrap_or_else(|| InlayId(post_inc(&mut self.next_inlay_id))), position: properties.position, text: properties.text.into(), }; - new_inlay_ids.push(inlay.id); + if existing_id.is_none() { + new_inlay_ids.push(inlay.id); + } self.inlays_by_id.insert(inlay.id, inlay.clone()); match self .inlays @@ -494,6 +511,10 @@ impl InlayMap { (snapshot, edits, new_inlay_ids) } + pub fn current_inlays(&self) -> impl Iterator { + self.inlays.iter() + } + #[cfg(test)] pub(crate) fn randomly_mutate( &mut self, @@ -519,10 +540,13 @@ impl InlayMap { bias, text ); - to_insert.push(InlayProperties { - position: snapshot.buffer.anchor_at(position, bias), - text, - }); + to_insert.push(( + None, + InlayProperties { + position: snapshot.buffer.anchor_at(position, bias), + text, + }, + )); } else { to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap()); } @@ -852,10 +876,13 @@ mod tests { let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), - vec![InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|123|", - }], + vec![( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|123|", + }, + )], ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( @@ -928,14 +955,20 @@ mod tests { let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), vec![ - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(3), - text: "|123|", - }, - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_after(3), - text: "|456|", - }, + ( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|", + }, + ), + ( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|", + }, + ), ], ); assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); @@ -963,18 +996,27 @@ mod tests { let (inlay_snapshot, _, _) = inlay_map.splice( Vec::new(), vec![ - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(0), - text: "|123|\n", - }, - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(4), - text: "|456|", - }, - InlayProperties { - position: buffer.read(cx).snapshot(cx).anchor_before(7), - text: "\n|567|\n", - }, + ( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(0), + text: "|123|\n", + }, + ), + ( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(4), + text: "|456|", + }, + ), + ( + None, + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(7), + text: "\n|567|\n", + }, + ), ], ); assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4a40d6ef2f2885e980adfddb1d9ac9ee91046836..d32dc47154eb62592a0826578d0c05089e0b50df 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,9 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_cache::{ - Inlay, InlayCache, InlayHintQuery, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, -}; +use inlay_cache::{InlayCache, InlayHintQuery, InlayRefreshReason, InlaySplice}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -2606,57 +2604,74 @@ impl Editor { } let multi_buffer_handle = self.buffer().clone(); + let currently_visible_ranges = self.excerpt_visible_offsets(&multi_buffer_handle, cx); + let currently_shown_inlays = self + .display_map + .read(cx) + .current_inlays() + .map(|inlay| (inlay.position, inlay.id)) + .collect::>(); match reason { InlayRefreshReason::SettingsChange(new_settings) => { - let InlaySplice { + if let Some(InlaySplice { to_remove, to_insert, - } = self.inlay_cache.apply_settings(new_settings); - self.splice_inlay_hints(to_remove, to_insert, cx); + }) = self.inlay_cache.apply_settings( + multi_buffer_handle, + new_settings, + currently_visible_ranges, + currently_shown_inlays, + cx, + ) { + self.splice_inlay_hints(to_remove, to_insert, cx); + } } InlayRefreshReason::Scroll(scrolled_to) => { - let addition_queries = self - .excerpt_visible_offsets(&multi_buffer_handle, cx) - .into_iter() - .find_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { + if let Some(updated_range_query) = currently_visible_ranges.iter().find_map( + |(buffer, excerpt_visible_offset_range, excerpt_id)| { let buffer_id = scrolled_to.anchor.buffer_id?; if buffer_id == buffer.read(cx).remote_id() - && scrolled_to.anchor.excerpt_id == excerpt_id + && &scrolled_to.anchor.excerpt_id == excerpt_id { - inlay_hint_query(&buffer, excerpt_id, excerpt_visible_offset_range, cx) + Some(inlay_hint_query( + buffer, + *excerpt_id, + excerpt_visible_offset_range, + cx, + )) } else { None } - }) - .into_iter(); - - cx.spawn(|editor, mut cx| async move { - let InlaySplice { - to_remove, - to_insert, - } = editor - .update(&mut cx, |editor, cx| { - editor.inlay_cache.append_inlays( - multi_buffer_handle, - addition_queries, - cx, - ) - })? - .await - .context("inlay cache hint fetch")?; + }, + ) { + cx.spawn(|editor, mut cx| async move { + let InlaySplice { + to_remove, + to_insert, + } = editor + .update(&mut cx, |editor, cx| { + editor.inlay_cache.append_inlays( + multi_buffer_handle, + std::iter::once(updated_range_query), + currently_shown_inlays, + cx, + ) + })? + .await + .context("inlay cache hint fetch")?; - editor.update(&mut cx, |editor, cx| { - editor.splice_inlay_hints(to_remove, to_insert, cx) + editor.update(&mut cx, |editor, cx| { + editor.splice_inlay_hints(to_remove, to_insert, cx) + }) }) - }) - .detach_and_log_err(cx); + .detach_and_log_err(cx); + } } InlayRefreshReason::VisibleExcerptsChange => { - let replacement_queries = self - .excerpt_visible_offsets(&multi_buffer_handle, cx) - .into_iter() - .filter_map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { - inlay_hint_query(&buffer, excerpt_id, excerpt_visible_offset_range, cx) + let replacement_queries = currently_visible_ranges + .iter() + .map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { + inlay_hint_query(buffer, *excerpt_id, excerpt_visible_offset_range, cx) }) .collect::>(); cx.spawn(|editor, mut cx| async move { @@ -2668,6 +2683,7 @@ impl Editor { editor.inlay_cache.replace_inlays( multi_buffer_handle, replacement_queries.into_iter(), + currently_shown_inlays, cx, ) })? @@ -2709,13 +2725,13 @@ impl Editor { fn splice_inlay_hints( &self, to_remove: Vec, - to_insert: Vec<(Anchor, project::InlayHint)>, + to_insert: Vec<(Option, Anchor, project::InlayHint)>, cx: &mut ViewContext, ) { let buffer = self.buffer.read(cx).read(cx); let new_inlays = to_insert .into_iter() - .map(|(hint_anchor, hint)| { + .map(|(id, hint_anchor, hint)| { let mut text = hint.text(); // TODO kb styling instead? if hint.padding_right { @@ -2725,10 +2741,13 @@ impl Editor { text.insert(0, ' '); } - InlayProperties { - position: hint_anchor.bias_left(&buffer), - text, - } + ( + id, + InlayProperties { + position: hint_anchor.bias_left(&buffer), + text, + }, + ) }) .collect(); drop(buffer); @@ -3482,15 +3501,23 @@ impl Editor { to_remove.push(suggestion.id); } - let to_insert = vec![InlayProperties { - position: cursor, - text: text.clone(), - }]; - self.display_map.update(cx, move |map, cx| { + let to_insert = vec![( + None, + InlayProperties { + position: cursor, + text: text.clone(), + }, + )]; + let new_inlay_ids = self.display_map.update(cx, move |map, cx| { map.splice_inlays(to_remove, to_insert, cx) }); + assert_eq!( + new_inlay_ids.len(), + 1, + "Expecting only copilot suggestion id generated" + ); self.copilot_state.suggestion = Some(Inlay { - id: InlayId(usize::MAX), + id: new_inlay_ids.into_iter().next().unwrap(), position: cursor, text, }); @@ -7652,9 +7679,9 @@ impl Editor { fn inlay_hint_query( buffer: &ModelHandle, excerpt_id: ExcerptId, - excerpt_visible_offset_range: Range, + excerpt_visible_offset_range: &Range, cx: &mut ViewContext<'_, '_, Editor>, -) -> Option { +) -> InlayHintQuery { let buffer = buffer.read(cx); let max_buffer_len = buffer.len(); let visible_offset_range_len = excerpt_visible_offset_range.len(); @@ -7667,12 +7694,12 @@ fn inlay_hint_query( .end .saturating_add(visible_offset_range_len), ); - Some(InlayHintQuery { + InlayHintQuery { buffer_id: buffer.remote_id(), buffer_version: buffer.version().clone(), excerpt_id, excerpt_offset_query_range: query_range_start..query_range_end, - }) + } } fn consume_contiguous_rows( diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_cache.rs index 7a362085147d514f7f71ea0a00e1308fec81b505..7d8dd67e78242dc8d760299106a0eef792761402 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_cache.rs @@ -1,26 +1,16 @@ use std::ops::Range; -use crate::{editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, MultiBuffer}; +use crate::{ + display_map::InlayId, editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, + MultiBuffer, +}; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; +use language::Buffer; use project::{InlayHint, InlayHintKind}; use collections::{HashMap, HashSet}; -// TODO kb move to inlay_map along with the next one? -#[derive(Debug, Clone)] -pub struct Inlay { - pub id: InlayId, - pub position: Anchor, - pub text: text::Rope, -} - -#[derive(Debug, Clone)] -pub struct InlayProperties { - pub position: Anchor, - pub text: T, -} - #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), @@ -30,23 +20,21 @@ pub enum InlayRefreshReason { #[derive(Debug, Clone, Default)] pub struct InlayCache { - inlays_per_buffer: HashMap, + inlay_hints: HashMap, + inlays_in_buffers: HashMap, allowed_hint_kinds: HashSet>, } #[derive(Clone, Debug, Default)] struct BufferInlays { buffer_version: Global, - ordered_by_anchor_inlays: Vec<(Anchor, InlayId, InlayHint)>, + ordered_by_anchor_inlays: Vec<(Anchor, InlayId)>, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InlayId(pub usize); - #[derive(Debug, Default)] pub struct InlaySplice { pub to_remove: Vec, - pub to_insert: Vec<(Anchor, InlayHint)>, + pub to_insert: Vec<(Option, Anchor, InlayHint)>, } pub struct InlayHintQuery { @@ -60,82 +48,99 @@ impl InlayCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), - inlays_per_buffer: HashMap::default(), + inlays_in_buffers: HashMap::default(), + inlay_hints: HashMap::default(), } } pub fn apply_settings( &mut self, + multi_buffer: ModelHandle, inlay_hint_settings: editor_settings::InlayHints, - ) -> InlaySplice { - let new_allowed_inlay_hint_types = allowed_inlay_hint_types(inlay_hint_settings); - - let new_allowed_hint_kinds = new_allowed_inlay_hint_types - .difference(&self.allowed_hint_kinds) - .copied() - .collect::>(); - let removed_hint_kinds = self - .allowed_hint_kinds - .difference(&new_allowed_inlay_hint_types) - .collect::>(); - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - for (_, inlay_id, inlay_hint) in self - .inlays_per_buffer - .iter() - .map(|(_, buffer_inlays)| buffer_inlays.ordered_by_anchor_inlays.iter()) - .flatten() - { - if removed_hint_kinds.contains(&inlay_hint.kind) { - to_remove.push(*inlay_id); - } else if new_allowed_hint_kinds.contains(&inlay_hint.kind) { - todo!("TODO kb: agree with InlayMap how splice works") - // to_insert.push((*inlay_id, *anchor, inlay_hint.to_owned())); + currently_visible_ranges: Vec<(ModelHandle, Range, ExcerptId)>, + currently_shown_inlays: Vec<(Anchor, InlayId)>, + cx: &mut ViewContext, + ) -> Option { + let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings); + if new_allowed_hint_kinds == self.allowed_hint_kinds { + None + } else { + self.allowed_hint_kinds = new_allowed_hint_kinds; + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + + let mut considered_inlay_ids = HashSet::default(); + for (_, shown_inlay_id) in currently_shown_inlays { + if let Some(inlay_hint) = self.inlay_hints.get(&shown_inlay_id) { + if !self.allowed_hint_kinds.contains(&inlay_hint.kind) { + to_remove.push(shown_inlay_id); + } + considered_inlay_ids.insert(shown_inlay_id); + } } - } - self.allowed_hint_kinds = new_allowed_hint_kinds; + let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); + for (inlay_id, inlay_hint) in &self.inlay_hints { + if self.allowed_hint_kinds.contains(&inlay_hint.kind) + && !considered_inlay_ids.contains(inlay_id) + { + if let Some(hint_to_readd) = currently_visible_ranges.iter() + .filter(|(_, visible_range, _)| visible_range.contains(&inlay_hint.position.offset)) + .find_map(|(_, _, excerpt_id)| { + let Some(anchor) = multi_buffer_snapshot + .find_anchor_in_excerpt(*excerpt_id, inlay_hint.position) else { return None; }; + Some((Some(*inlay_id), anchor, inlay_hint.clone())) + }, + ) { + to_insert.push(hint_to_readd); + } + } + } - InlaySplice { - to_remove, - to_insert, + Some(InlaySplice { + to_remove, + to_insert, + }) } } pub fn clear(&mut self) -> Vec { - self.inlays_per_buffer - .drain() - .flat_map(|(_, buffer_inlays)| { - buffer_inlays - .ordered_by_anchor_inlays - .into_iter() - .map(|(_, id, _)| id) - }) - .collect() + let ids_to_remove = self.inlay_hints.drain().map(|(id, _)| id).collect(); + self.inlays_in_buffers.clear(); + ids_to_remove } pub fn append_inlays( &mut self, multi_buffer: ModelHandle, ranges_to_add: impl Iterator, + currently_shown_inlays: Vec<(Anchor, InlayId)>, cx: &mut ViewContext, ) -> Task> { - self.fetch_inlays(multi_buffer, ranges_to_add, false, cx) + self.fetch_inlays( + multi_buffer, + ranges_to_add, + currently_shown_inlays, + false, + cx, + ) } pub fn replace_inlays( &mut self, multi_buffer: ModelHandle, new_ranges: impl Iterator, + currently_shown_inlays: Vec<(Anchor, InlayId)>, cx: &mut ViewContext, ) -> Task> { - self.fetch_inlays(multi_buffer, new_ranges, true, cx) + self.fetch_inlays(multi_buffer, new_ranges, currently_shown_inlays, true, cx) } fn fetch_inlays( &mut self, multi_buffer: ModelHandle, inlay_fetch_ranges: impl Iterator, + currently_shown_inlays: Vec<(Anchor, InlayId)>, replace_old: bool, cx: &mut ViewContext, ) -> Task> { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 31af03f768ea549d04a8802623bbc364acd762a4..0a700879454931493bc16362669751163779abe8 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2617,6 +2617,15 @@ impl MultiBufferSnapshot { } pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { + self.find_anchor_in_excerpt(excerpt_id, text_anchor) + .unwrap_or_else(|| panic!("excerpt not found")) + } + + pub fn find_anchor_in_excerpt( + &self, + excerpt_id: ExcerptId, + text_anchor: text::Anchor, + ) -> Option { let locator = self.excerpt_locator_for_id(excerpt_id); let mut cursor = self.excerpts.cursor::>(); cursor.seek(locator, Bias::Left, &()); @@ -2624,14 +2633,15 @@ impl MultiBufferSnapshot { if excerpt.id == excerpt_id { let text_anchor = excerpt.clip_anchor(text_anchor); drop(cursor); - return Anchor { + return Some(Anchor { buffer_id: Some(excerpt.buffer_id), excerpt_id, text_anchor, - }; + }); } } - panic!("excerpt not found"); + + None } pub fn can_resolve(&self, anchor: &Anchor) -> bool { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fb2cef9863934d1a00ff8d81721eeba1b5aae28e..89975a57465307c86a4ae2782f8a8f356e9ce2dc 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,7 @@ use language::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, }, - range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel, + range_from_lsp, range_to_lsp, Bias, Buffer, 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, @@ -76,6 +76,7 @@ use std::{ time::{Duration, Instant}, }; 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 _, @@ -330,7 +331,7 @@ pub struct Location { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlayHint { pub buffer_id: u64, - pub position: Anchor, + pub position: language::Anchor, pub label: InlayHintLabel, pub kind: Option, pub padding_left: bool, From 5322aa09b93f7c76de9867060098666a323dbf60 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 17 Jun 2023 14:29:49 +0300 Subject: [PATCH 109/169] Properly handle settings toggle --- crates/editor/src/editor.rs | 42 +++-- .../{inlay_cache.rs => inlay_hint_cache.rs} | 170 ++++++++++++++---- crates/editor/src/multi_buffer.rs | 15 +- crates/editor/src/scroll.rs | 2 +- 4 files changed, 164 insertions(+), 65 deletions(-) rename crates/editor/src/{inlay_cache.rs => inlay_hint_cache.rs} (74%) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d32dc47154eb62592a0826578d0c05089e0b50df..fc3319dc56c438ac81993254912c0e3ebe79515b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3,7 +3,7 @@ mod blink_manager; pub mod display_map; mod editor_settings; mod element; -mod inlay_cache; +mod inlay_hint_cache; mod git; mod highlight_matching_bracket; @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_cache::{InlayCache, InlayHintQuery, InlayRefreshReason, InlaySplice}; +use inlay_hint_cache::{InlayHintCache, InlayHintQuery, InlayRefreshReason, InlaySplice}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -540,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_cache: InlayCache, + inlay_cache: InlayHintCache, _subscriptions: Vec, } @@ -1355,7 +1355,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_cache: InlayCache::new(settings::get::(cx).inlay_hints), + inlay_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2604,23 +2604,37 @@ impl Editor { } let multi_buffer_handle = self.buffer().clone(); + let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx); let currently_visible_ranges = self.excerpt_visible_offsets(&multi_buffer_handle, cx); - let currently_shown_inlays = self - .display_map - .read(cx) - .current_inlays() - .map(|inlay| (inlay.position, inlay.id)) - .collect::>(); + let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold( + HashMap::>>::default(), + |mut current_hints, inlay| { + if let Some(buffer_id) = inlay.position.buffer_id { + let excerpt_hints = current_hints + .entry(buffer_id) + .or_default() + .entry(inlay.position.excerpt_id) + .or_default(); + match excerpt_hints.binary_search_by(|probe| { + inlay.position.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => { + excerpt_hints.insert(ix, (inlay.position, inlay.id)); + } + } + } + current_hints + }, + ); match reason { InlayRefreshReason::SettingsChange(new_settings) => { if let Some(InlaySplice { to_remove, to_insert, }) = self.inlay_cache.apply_settings( - multi_buffer_handle, new_settings, currently_visible_ranges, - currently_shown_inlays, + currently_shown_inlay_hints, cx, ) { self.splice_inlay_hints(to_remove, to_insert, cx); @@ -2653,7 +2667,7 @@ impl Editor { editor.inlay_cache.append_inlays( multi_buffer_handle, std::iter::once(updated_range_query), - currently_shown_inlays, + currently_shown_inlay_hints, cx, ) })? @@ -2683,7 +2697,7 @@ impl Editor { editor.inlay_cache.replace_inlays( multi_buffer_handle, replacement_queries.into_iter(), - currently_shown_inlays, + currently_shown_inlay_hints, cx, ) })? diff --git a/crates/editor/src/inlay_cache.rs b/crates/editor/src/inlay_hint_cache.rs similarity index 74% rename from crates/editor/src/inlay_cache.rs rename to crates/editor/src/inlay_hint_cache.rs index 7d8dd67e78242dc8d760299106a0eef792761402..4754e2a186b8b28c9d9e62d479e45a2f41db1dc7 100644 --- a/crates/editor/src/inlay_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -9,7 +9,7 @@ use gpui::{ModelHandle, Task, ViewContext}; use language::Buffer; use project::{InlayHint, InlayHintKind}; -use collections::{HashMap, HashSet}; +use collections::{hash_map, HashMap, HashSet}; #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { @@ -19,16 +19,25 @@ pub enum InlayRefreshReason { } #[derive(Debug, Clone, Default)] -pub struct InlayCache { +pub struct InlayHintCache { inlay_hints: HashMap, - inlays_in_buffers: HashMap, + inlays_in_buffers: HashMap>, allowed_hint_kinds: HashSet>, } #[derive(Clone, Debug, Default)] -struct BufferInlays { +struct BufferInlays { buffer_version: Global, - ordered_by_anchor_inlays: Vec<(Anchor, InlayId)>, + excerpt_inlays: HashMap>, +} + +impl BufferInlays { + fn new(buffer_version: Global) -> Self { + Self { + buffer_version, + excerpt_inlays: HashMap::default(), + } + } } #[derive(Debug, Default)] @@ -44,7 +53,7 @@ pub struct InlayHintQuery { pub excerpt_offset_query_range: Range, } -impl InlayCache { +impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), @@ -55,10 +64,9 @@ impl InlayCache { pub fn apply_settings( &mut self, - multi_buffer: ModelHandle, inlay_hint_settings: editor_settings::InlayHints, currently_visible_ranges: Vec<(ModelHandle, Range, ExcerptId)>, - currently_shown_inlays: Vec<(Anchor, InlayId)>, + mut currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, ) -> Option { let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings); @@ -69,33 +77,88 @@ impl InlayCache { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut considered_inlay_ids = HashSet::default(); - for (_, shown_inlay_id) in currently_shown_inlays { - if let Some(inlay_hint) = self.inlay_hints.get(&shown_inlay_id) { - if !self.allowed_hint_kinds.contains(&inlay_hint.kind) { - to_remove.push(shown_inlay_id); + let mut considered_hints = + HashMap::>>::default(); + for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges { + let visible_buffer = visible_buffer.read(cx); + let visible_buffer_id = visible_buffer.remote_id(); + match currently_shown_inlay_hints.entry(visible_buffer_id) { + hash_map::Entry::Occupied(mut o) => { + let shown_hints_per_excerpt = o.get_mut(); + for (_, shown_hint_id) in shown_hints_per_excerpt + .remove(&visible_excerpt_id) + .unwrap_or_default() + { + considered_hints + .entry(visible_buffer_id) + .or_default() + .entry(visible_excerpt_id) + .or_default() + .insert(shown_hint_id); + match self.inlay_hints.get(&shown_hint_id) { + Some(shown_hint) => { + if !self.allowed_hint_kinds.contains(&shown_hint.kind) { + to_remove.push(shown_hint_id); + } + } + None => to_remove.push(shown_hint_id), + } + } + if shown_hints_per_excerpt.is_empty() { + o.remove(); + } } - considered_inlay_ids.insert(shown_inlay_id); + hash_map::Entry::Vacant(_) => {} } } - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - for (inlay_id, inlay_hint) in &self.inlay_hints { - if self.allowed_hint_kinds.contains(&inlay_hint.kind) - && !considered_inlay_ids.contains(inlay_id) - { - if let Some(hint_to_readd) = currently_visible_ranges.iter() - .filter(|(_, visible_range, _)| visible_range.contains(&inlay_hint.position.offset)) - .find_map(|(_, _, excerpt_id)| { - let Some(anchor) = multi_buffer_snapshot - .find_anchor_in_excerpt(*excerpt_id, inlay_hint.position) else { return None; }; - Some((Some(*inlay_id), anchor, inlay_hint.clone())) - }, - ) { - to_insert.push(hint_to_readd); - } - } - } + let reenabled_hints = self + .inlays_in_buffers + .iter() + .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| { + let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?; + let not_considered_cached_inlays = cached_hints_per_excerpt + .excerpt_inlays + .iter() + .filter_map(|(cached_excerpt_id, cached_hints)| { + let considered_excerpt_hints = + considered_hints_in_excerpts.get(&cached_excerpt_id)?; + let not_considered_cached_inlays = cached_hints + .iter() + .filter(|(_, cached_hint_id)| { + !considered_excerpt_hints.contains(cached_hint_id) + }) + .copied(); + Some(not_considered_cached_inlays) + }) + .flatten(); + Some(not_considered_cached_inlays) + }) + .flatten() + .filter_map(|(cached_anchor, cached_inlay_id)| { + Some(( + cached_anchor, + cached_inlay_id, + self.inlay_hints.get(&cached_inlay_id)?, + )) + }) + .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind)) + .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| { + ( + Some(cached_inlay_id), + cached_anchor, + reenabled_inlay.clone(), + ) + }); + to_insert.extend(reenabled_hints); + + to_remove.extend( + currently_shown_inlay_hints + .into_iter() + .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) + .flat_map(|(_, excerpt_hints)| excerpt_hints) + .map(|(_, hint_id)| hint_id), + ); Some(InlaySplice { to_remove, @@ -114,13 +177,13 @@ impl InlayCache { &mut self, multi_buffer: ModelHandle, ranges_to_add: impl Iterator, - currently_shown_inlays: Vec<(Anchor, InlayId)>, + currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { self.fetch_inlays( multi_buffer, ranges_to_add, - currently_shown_inlays, + currently_shown_inlay_hints, false, cx, ) @@ -130,21 +193,52 @@ impl InlayCache { &mut self, multi_buffer: ModelHandle, new_ranges: impl Iterator, - currently_shown_inlays: Vec<(Anchor, InlayId)>, + currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { - self.fetch_inlays(multi_buffer, new_ranges, currently_shown_inlays, true, cx) + self.fetch_inlays( + multi_buffer, + new_ranges, + currently_shown_inlay_hints, + true, + cx, + ) } fn fetch_inlays( &mut self, multi_buffer: ModelHandle, - inlay_fetch_ranges: impl Iterator, - currently_shown_inlays: Vec<(Anchor, InlayId)>, + inlay_queries: impl Iterator, + mut currently_shown_inlay_hints: HashMap>>, replace_old: bool, cx: &mut ViewContext, ) -> Task> { - // TODO kb + let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); + let inlay_queries_per_buffer = inlay_queries.fold( + HashMap::>::default(), + |mut queries, new_query| { + let mut buffer_queries = queries + .entry(new_query.buffer_id) + .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); + assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); + let queries = buffer_queries + .excerpt_inlays + .entry(new_query.excerpt_id) + .or_default(); + // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); + // .push(new_query); + // match queries + // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) + // { + // Ok(ix) | Err(ix) => { + // excerpt_hints.insert(ix, (inlay.position, inlay.id)); + // } + // } + // queries + todo!("TODO kb") + }, + ); + todo!("TODO kb") } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 0a700879454931493bc16362669751163779abe8..d4298efacf1c4f5a051fd94422cbc62d90f2275d 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2617,15 +2617,6 @@ impl MultiBufferSnapshot { } pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { - self.find_anchor_in_excerpt(excerpt_id, text_anchor) - .unwrap_or_else(|| panic!("excerpt not found")) - } - - pub fn find_anchor_in_excerpt( - &self, - excerpt_id: ExcerptId, - text_anchor: text::Anchor, - ) -> Option { let locator = self.excerpt_locator_for_id(excerpt_id); let mut cursor = self.excerpts.cursor::>(); cursor.seek(locator, Bias::Left, &()); @@ -2633,15 +2624,15 @@ impl MultiBufferSnapshot { if excerpt.id == excerpt_id { let text_anchor = excerpt.clip_anchor(text_anchor); drop(cursor); - return Some(Anchor { + return Anchor { buffer_id: Some(excerpt.buffer_id), excerpt_id, text_anchor, - }); + }; } } - None + panic!("excerpt not found") } pub fn can_resolve(&self, anchor: &Anchor) -> bool { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index e0ca2a1ebf534fc5c2e26ebdbc33c21cea8d52ed..b5343359b58fe7c346a0c35cb48d09460aaba560 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -18,7 +18,7 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, - inlay_cache::InlayRefreshReason, + inlay_hint_cache::InlayRefreshReason, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; From e82b4d8957080c35af4633f786f4a95a2ed3567e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 17 Jun 2023 22:26:29 +0300 Subject: [PATCH 110/169] Properly handle hint addition queries --- crates/editor/src/editor.rs | 13 +- crates/editor/src/inlay_hint_cache.rs | 591 ++++++++++++-------------- 2 files changed, 268 insertions(+), 336 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fc3319dc56c438ac81993254912c0e3ebe79515b..03e98407db7b457424424e31f437651aeedeb0c8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -540,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_cache: InlayHintCache, + inlay_hint_cache: InlayHintCache, _subscriptions: Vec, } @@ -1355,7 +1355,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), + inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2598,7 +2598,7 @@ impl Editor { } if !settings::get::(cx).inlay_hints.enabled { - let to_remove = self.inlay_cache.clear(); + let to_remove = self.inlay_hint_cache.clear(); self.splice_inlay_hints(to_remove, Vec::new(), cx); return; } @@ -2631,7 +2631,7 @@ impl Editor { if let Some(InlaySplice { to_remove, to_insert, - }) = self.inlay_cache.apply_settings( + }) = self.inlay_hint_cache.apply_settings( new_settings, currently_visible_ranges, currently_shown_inlay_hints, @@ -2664,10 +2664,9 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.append_inlays( + editor.inlay_hint_cache.append_inlays( multi_buffer_handle, std::iter::once(updated_range_query), - currently_shown_inlay_hints, cx, ) })? @@ -2694,7 +2693,7 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.replace_inlays( + editor.inlay_hint_cache.replace_inlays( multi_buffer_handle, replacement_queries.into_iter(), currently_shown_inlay_hints, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 4754e2a186b8b28c9d9e62d479e45a2f41db1dc7..7f3124b5e83ab3d5c23a7c6a9caec1a9a626178b 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -4,9 +4,11 @@ use crate::{ display_map::InlayId, editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, MultiBuffer, }; +use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; use language::Buffer; +use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; @@ -28,6 +30,7 @@ pub struct InlayHintCache { #[derive(Clone, Debug, Default)] struct BufferInlays { buffer_version: Global, + cached_ranges: HashMap>>, excerpt_inlays: HashMap>, } @@ -36,6 +39,7 @@ impl BufferInlays { Self { buffer_version, excerpt_inlays: HashMap::default(), + cached_ranges: HashMap::default(), } } } @@ -177,16 +181,95 @@ impl InlayHintCache { &mut self, multi_buffer: ModelHandle, ranges_to_add: impl Iterator, - currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { - self.fetch_inlays( - multi_buffer, - ranges_to_add, - currently_shown_inlay_hints, - false, - cx, - ) + let queries = ranges_to_add.filter_map(|additive_query| { + let Some(cached_buffer_inlays) = self.inlays_in_buffers.get(&additive_query.buffer_id) + else { return Some(vec![additive_query]) }; + if cached_buffer_inlays.buffer_version.changed_since(&additive_query.buffer_version) { + return None + } + let Some(excerpt_cached_ranges) = cached_buffer_inlays.cached_ranges.get(&additive_query.excerpt_id) + else { return Some(vec![additive_query]) }; + let non_cached_ranges = missing_subranges(&excerpt_cached_ranges, &additive_query.excerpt_offset_query_range); + if non_cached_ranges.is_empty() { + None + } else { + Some(non_cached_ranges.into_iter().map(|non_cached_range| InlayHintQuery { + buffer_id: additive_query.buffer_id, + buffer_version: additive_query.buffer_version.clone(), + excerpt_id: additive_query.excerpt_id, + excerpt_offset_query_range: non_cached_range, + }).collect()) + } + }).flatten(); + + let task_multi_buffer = multi_buffer.clone(); + let fetch_queries_task = fetch_queries(multi_buffer, queries, cx); + cx.spawn(|editor, mut cx| async move { + let new_hints = fetch_queries_task.await?; + editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); + let inlay_hint_cache = &mut editor.inlay_hint_cache; + let mut to_insert = Vec::new(); + for (new_buffer_id, new_hints_per_buffer) in new_hints { + let cached_buffer_inlays = inlay_hint_cache + .inlays_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferInlays::new(new_hints_per_buffer.buffer_version.clone()) + }); + if cached_buffer_inlays + .buffer_version + .changed_since(&new_hints_per_buffer.buffer_version) + { + continue; + } + + for (new_excerpt_id, new_ranges) in new_hints_per_buffer.cached_ranges { + let cached_ranges = cached_buffer_inlays + .cached_ranges + .entry(new_excerpt_id) + .or_default(); + for new_range in new_ranges { + insert_and_merge_ranges(cached_ranges, &new_range) + } + } + for (new_excerpt_id, new_hints) in new_hints_per_buffer.excerpt_inlays { + let cached_inlays = cached_buffer_inlays + .excerpt_inlays + .entry(new_excerpt_id) + .or_default(); + for new_inlay_hint in new_hints { + let new_inlay_id = todo!("TODO kb"); + let hint_anchor = multi_buffer_snapshot + .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position); + match cached_inlays.binary_search_by(|probe| { + hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => { + cached_inlays.insert(ix, (hint_anchor, new_inlay_id)) + } + } + inlay_hint_cache + .inlay_hints + .insert(new_inlay_id, new_inlay_hint.clone()); + if inlay_hint_cache + .allowed_hint_kinds + .contains(&new_inlay_hint.kind) + { + to_insert.push((Some(new_inlay_id), hint_anchor, new_inlay_hint)); + } + } + } + } + + InlaySplice { + to_remove: Vec::new(), + to_insert, + } + }) + }) } pub fn replace_inlays( @@ -195,332 +278,35 @@ impl InlayHintCache { new_ranges: impl Iterator, currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, - ) -> Task> { - self.fetch_inlays( - multi_buffer, - new_ranges, - currently_shown_inlay_hints, - true, - cx, - ) - } - - fn fetch_inlays( - &mut self, - multi_buffer: ModelHandle, - inlay_queries: impl Iterator, - mut currently_shown_inlay_hints: HashMap>>, - replace_old: bool, - cx: &mut ViewContext, ) -> Task> { let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let inlay_queries_per_buffer = inlay_queries.fold( - HashMap::>::default(), - |mut queries, new_query| { - let mut buffer_queries = queries - .entry(new_query.buffer_id) - .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); - assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); - let queries = buffer_queries - .excerpt_inlays - .entry(new_query.excerpt_id) - .or_default(); - // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); - // .push(new_query); - // match queries - // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) - // { - // Ok(ix) | Err(ix) => { - // excerpt_hints.insert(ix, (inlay.position, inlay.id)); - // } - // } - // queries - todo!("TODO kb") - }, - ); + // let inlay_queries_per_buffer = inlay_queries.fold( + // HashMap::>::default(), + // |mut queries, new_query| { + // let mut buffer_queries = queries + // .entry(new_query.buffer_id) + // .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); + // assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); + // let queries = buffer_queries + // .excerpt_inlays + // .entry(new_query.excerpt_id) + // .or_default(); + // // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); + // // .push(new_query); + // // match queries + // // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) + // // { + // // Ok(ix) | Err(ix) => { + // // excerpt_hints.insert(ix, (inlay.position, inlay.id)); + // // } + // // } + // // queries + // todo!("TODO kb") + // }, + // ); todo!("TODO kb") } - - // fn fetch_inlays( - // &mut self, - // multi_buffer: ModelHandle, - // inlay_fetch_ranges: impl Iterator, - // replace_old: bool, - // cx: &mut ViewContext, - // ) -> Task> { - // let mut inlay_fetch_tasks = Vec::new(); - // for inlay_fetch_range in inlay_fetch_ranges { - // let inlays_up_to_date = self.inlays_up_to_date( - // &inlay_fetch_range.buffer_path, - // &inlay_fetch_range.buffer_version, - // inlay_fetch_range.excerpt_id, - // ); - // let task_multi_buffer = multi_buffer.clone(); - // let task = cx.spawn(|editor, mut cx| async move { - // if inlays_up_to_date { - // anyhow::Ok((inlay_fetch_range, None)) - // } else { - // let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id)) - // else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; - // let task = editor - // .update(&mut cx, |editor, cx| { - // editor.project.as_ref().map(|project| { - // project.update(cx, |project, cx| { - // project.query_inlay_hints_for_buffer( - // buffer_handle, - // inlay_fetch_range.excerpt_offset_query_range.clone(), - // cx, - // ) - // }) - // }) - // }) - // .context("inlays fecth task spawn")?; - - // Ok((inlay_fetch_range, match task { - // Some(task) => task.await.context("inlays for buffer task")?, - // None => Some(Vec::new()), - // })) - // } - // }); - // inlay_fetch_tasks.push(task); - // } - - // let final_task = cx.spawn(|editor, mut cx| async move { - // let mut inlay_updates: HashMap< - // PathBuf, - // ( - // Global, - // HashMap, OrderedByAnchorOffset)>>, - // ), - // > = HashMap::default(); - // let multi_buffer_snapshot = - // editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; - - // for task_result in futures::future::join_all(inlay_fetch_tasks).await { - // match task_result { - // Ok((inlay_fetch_range, response_inlays)) => { - // // TODO kb different caching now - // let inlays_per_excerpt = HashMap::from_iter([( - // inlay_fetch_range.excerpt_id, - // response_inlays - // .map(|excerpt_inlays| { - // excerpt_inlays.into_iter().fold( - // OrderedByAnchorOffset::default(), - // |mut ordered_inlays, inlay| { - // let anchor = multi_buffer_snapshot.anchor_in_excerpt( - // inlay_fetch_range.excerpt_id, - // inlay.position, - // ); - // ordered_inlays.add(anchor, inlay); - // ordered_inlays - // }, - // ) - // }) - // .map(|inlays| { - // (inlay_fetch_range.excerpt_offset_query_range, inlays) - // }), - // )]); - // match inlay_updates.entry(inlay_fetch_range.buffer_path) { - // hash_map::Entry::Occupied(mut o) => { - // o.get_mut().1.extend(inlays_per_excerpt); - // } - // hash_map::Entry::Vacant(v) => { - // v.insert((inlay_fetch_range.buffer_version, inlays_per_excerpt)); - // } - // } - // } - // Err(e) => error!("Failed to update inlays for buffer: {e:#}"), - // } - // } - - // let updates = if !inlay_updates.is_empty() { - // let inlays_update = editor.update(&mut cx, |editor, _| { - // editor.inlay_cache.apply_fetch_inlays(inlay_updates) - // })?; - // inlays_update - // } else { - // InlaySplice::default() - // }; - - // anyhow::Ok(updates) - // }); - - // final_task - // } - - // fn inlays_up_to_date( - // &self, - // buffer_path: &Path, - // buffer_version: &Global, - // excerpt_id: ExcerptId, - // ) -> bool { - // let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; - // let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version - // || buffer_inlays.buffer_version.changed_since(&buffer_version); - // buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) - // } - - // fn apply_fetch_inlays( - // &mut self, - // fetched_inlays: HashMap< - // PathBuf, - // ( - // Global, - // HashMap, OrderedByAnchorOffset)>>, - // ), - // >, - // ) -> InlaySplice { - // let mut old_inlays = self.inlays_per_buffer.clone(); - // let mut to_remove = Vec::new(); - // let mut to_insert = Vec::new(); - - // for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays { - // match old_inlays.remove(&buffer_path) { - // Some(mut old_buffer_inlays) => { - // for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { - // let (_, mut new_excerpt_inlays) = match new_excerpt_inlays { - // Some((excerpt_offset_range, new_inlays)) => ( - // excerpt_offset_range, - // new_inlays.into_ordered_elements().fuse().peekable(), - // ), - // None => continue, - // }; - // if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { - // continue; - // } - - // let self_inlays_per_buffer = self - // .inlays_per_buffer - // .get_mut(&buffer_path) - // .expect("element expected: `old_inlays.remove` returned `Some`"); - - // if old_buffer_inlays - // .inlays_per_excerpts - // .remove(&excerpt_id) - // .is_some() - // { - // let self_excerpt_inlays = self_inlays_per_buffer - // .inlays_per_excerpts - // .get_mut(&excerpt_id) - // .expect("element expected: `old_excerpt_inlays` is `Some`"); - // let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); - // // TODO kb update inner buffer_id and version with the new data? - // self_excerpt_inlays.0.retain( - // |_, (old_anchor, (old_inlay_id, old_inlay))| { - // let mut retain = false; - - // while let Some(new_offset) = new_excerpt_inlays - // .peek() - // .map(|(new_anchor, _)| new_anchor.text_anchor.offset) - // { - // let old_offset = old_anchor.text_anchor.offset; - // match new_offset.cmp(&old_offset) { - // cmp::Ordering::Less => { - // let (new_anchor, new_inlay) = - // new_excerpt_inlays.next().expect( - // "element expected: `peek` returned `Some`", - // ); - // hints_to_add.push(( - // new_anchor, - // ( - // InlayId(post_inc(&mut self.next_inlay_id)), - // new_inlay, - // ), - // )); - // } - // cmp::Ordering::Equal => { - // let (new_anchor, new_inlay) = - // new_excerpt_inlays.next().expect( - // "element expected: `peek` returned `Some`", - // ); - // if &new_inlay == old_inlay { - // retain = true; - // } else { - // hints_to_add.push(( - // new_anchor, - // ( - // InlayId(post_inc( - // &mut self.next_inlay_id, - // )), - // new_inlay, - // ), - // )); - // } - // } - // cmp::Ordering::Greater => break, - // } - // } - - // if !retain { - // to_remove.push(*old_inlay_id); - // } - // retain - // }, - // ); - - // for (new_anchor, (id, new_inlay)) in hints_to_add { - // self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - - // for (new_anchor, new_inlay) in new_excerpt_inlays { - // let id = InlayId(post_inc(&mut self.next_inlay_id)); - // self_inlays_per_buffer - // .inlays_per_excerpts - // .entry(excerpt_id) - // .or_default() - // .add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - // } - // None => { - // let mut inlays_per_excerpts: HashMap< - // ExcerptId, - // OrderedByAnchorOffset<(InlayId, InlayHint)>, - // > = HashMap::default(); - // for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { - // if let Some((_, new_ordered_inlays)) = new_ordered_inlays { - // for (new_anchor, new_inlay) in - // new_ordered_inlays.into_ordered_elements() - // { - // let id = InlayId(post_inc(&mut self.next_inlay_id)); - // inlays_per_excerpts - // .entry(new_excerpt_id) - // .or_default() - // .add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - // } - // self.inlays_per_buffer.insert( - // buffer_path, - // BufferInlays { - // buffer_version, - // inlays_per_excerpts, - // }, - // ); - // } - // } - // } - - // for (_, old_buffer_inlays) in old_inlays { - // for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts { - // for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() { - // to_remove.push(id_to_remove); - // } - // } - // } - - // to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); - - // InlaySplice { - // to_remove, - // to_insert, - // } - // } } fn allowed_inlay_hint_types( @@ -538,3 +324,150 @@ fn allowed_inlay_hint_types( } new_allowed_inlay_hint_types } + +fn missing_subranges(cache: &[Range], input: &Range) -> Vec> { + let mut missing = Vec::new(); + + // Find where the input range would fit in the cache + let index = match cache.binary_search_by_key(&input.start, |probe| probe.start) { + Ok(pos) | Err(pos) => pos, + }; + + // Check for a gap from the start of the input range to the first range in the cache + if index == 0 { + if input.start < cache[index].start { + missing.push(input.start..cache[index].start); + } + } else { + let prev_end = cache[index - 1].end; + if input.start < prev_end { + missing.push(input.start..prev_end); + } + } + + // Iterate through the cache ranges starting from index + for i in index..cache.len() { + let start = if i > 0 { cache[i - 1].end } else { input.start }; + let end = cache[i].start; + + if start < end { + missing.push(start..end); + } + } + + // Check for a gap from the last range in the cache to the end of the input range + if let Some(last_range) = cache.last() { + if last_range.end < input.end { + missing.push(last_range.end..input.end); + } + } else { + // If cache is empty, the entire input range is missing + missing.push(input.start..input.end); + } + + missing +} + +fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range) { + if cache.is_empty() { + cache.push(new_range.clone()); + return; + } + + // Find the index to insert the new range + let index = match cache.binary_search_by_key(&new_range.start, |probe| probe.start) { + Ok(pos) | Err(pos) => pos, + }; + + // Check if the new range overlaps with the previous range in the cache + if index > 0 && cache[index - 1].end >= new_range.start { + // Merge with the previous range + cache[index - 1].end = std::cmp::max(cache[index - 1].end, new_range.end); + } else { + // Insert the new range, as it doesn't overlap with the previous range + cache.insert(index, new_range.clone()); + } + + // Merge overlaps with subsequent ranges + let mut i = index; + while i + 1 < cache.len() && cache[i].end >= cache[i + 1].start { + cache[i].end = std::cmp::max(cache[i].end, cache[i + 1].end); + cache.remove(i + 1); + i += 1; + } +} + +fn fetch_queries<'a, 'b>( + multi_buffer: ModelHandle, + queries: impl Iterator, + cx: &mut ViewContext<'a, 'b, Editor>, +) -> Task>>> { + let mut inlay_fetch_tasks = Vec::new(); + for query in queries { + let task_multi_buffer = multi_buffer.clone(); + let task = cx.spawn(|editor, mut cx| async move { + let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id)) + else { return anyhow::Ok((query, Some(Vec::new()))) }; + let task = editor + .update(&mut cx, |editor, cx| { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + project.query_inlay_hints_for_buffer( + buffer_handle, + query.excerpt_offset_query_range.clone(), + cx, + ) + }) + }) + }) + .context("inlays fecth task spawn")?; + Ok(( + query, + match task { + Some(task) => task.await.context("inlays for buffer task")?, + None => Some(Vec::new()), + }, + )) + }); + + inlay_fetch_tasks.push(task); + } + + cx.spawn(|editor, cx| async move { + let mut inlay_updates: HashMap> = HashMap::default(); + for task_result in futures::future::join_all(inlay_fetch_tasks).await { + match task_result { + Ok((query, Some(response_inlays))) => { + let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| { + editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot()) + })? else { continue; }; + let buffer_inlays = inlay_updates + .entry(query.buffer_id) + .or_insert_with(|| BufferInlays::new(query.buffer_version.clone())); + assert_eq!(buffer_inlays.buffer_version, query.buffer_version); + { + let cached_ranges = buffer_inlays + .cached_ranges + .entry(query.excerpt_id) + .or_default(); + insert_and_merge_ranges(cached_ranges, &query.excerpt_offset_query_range); + let excerpt_inlays = buffer_inlays + .excerpt_inlays + .entry(query.excerpt_id) + .or_default(); + for inlay in response_inlays { + match excerpt_inlays.binary_search_by(|probe| { + inlay.position.cmp(&probe.position, &buffer_snapshot) + }) { + Ok(ix) | Err(ix) => excerpt_inlays.insert(ix, inlay), + } + } + } + } + Ok((_, None)) => {} + Err(e) => error!("Failed to update inlays for buffer: {e:#}"), + } + } + Ok(inlay_updates) + }) +} From 8c03e9e1229c7fcd36bfa3b207c05996713cc8f8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 17 Jun 2023 23:10:48 +0300 Subject: [PATCH 111/169] Move InlayId generation back to InlayCache --- crates/editor/src/display_map.rs | 13 +- crates/editor/src/display_map/fold_map.rs | 3 +- crates/editor/src/display_map/inlay_map.rs | 54 +++---- crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/display_map/wrap_map.rs | 4 +- crates/editor/src/editor.rs | 27 ++-- crates/editor/src/inlay_hint_cache.rs | 177 +++++++++++---------- 7 files changed, 147 insertions(+), 133 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 791665bf78374f1d4d5c874e49c123836bfde424..16d44fbacfb9aaa806ed018ed48f7dbc3623f075 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,7 +4,7 @@ mod inlay_map; mod tab_map; mod wrap_map; -use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; +use crate::{Anchor, AnchorRangeExt, InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::FoldMap; @@ -28,7 +28,7 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; -pub use self::inlay_map::{Inlay, InlayId, InlayProperties}; +pub use self::inlay_map::{Inlay, InlayProperties}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { @@ -249,11 +249,11 @@ impl DisplayMap { pub fn splice_inlays>( &mut self, to_remove: Vec, - to_insert: Vec<(Option, InlayProperties)>, + to_insert: Vec<(InlayId, InlayProperties)>, cx: &mut ModelContext, - ) -> Vec { + ) { if to_remove.is_empty() && to_insert.is_empty() { - return Vec::new(); + return; } let buffer_snapshot = self.buffer.read(cx).snapshot(cx); @@ -267,14 +267,13 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - let (snapshot, edits, new_inlay_ids) = self.inlay_map.splice(to_remove, to_insert); + let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); - new_inlay_ids } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index fb0892b82d92152c8732cdfb8fcd8ef1c651d654..fe3c0d86abd18661b1be39ec6b1a7bd36b5b5fb0 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1475,6 +1475,7 @@ mod tests { Arc::new((HighlightStyle::default(), highlight_ranges)), ); + let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); let mut buffer_edits = Vec::new(); @@ -1484,7 +1485,7 @@ mod tests { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } 40..=59 => { - let (_, edits) = inlay_map.randomly_mutate(&mut rng); + let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); inlay_edits = edits; } _ => buffer.update(cx, |buffer, cx| { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 0c7d1bd60396fb95a6e2989bb7fbd23c2d7c080f..43f86287d8080c519cf377c304d3d96478066d70 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,6 +1,6 @@ use crate::{ multi_buffer::{MultiBufferChunks, MultiBufferRows}, - Anchor, MultiBufferSnapshot, ToOffset, + Anchor, InlayId, MultiBufferSnapshot, ToOffset, }; use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; @@ -12,13 +12,11 @@ use std::{ }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; -use util::post_inc; pub struct InlayMap { snapshot: Mutex, inlays_by_id: HashMap, inlays: Vec, - next_inlay_id: usize, } #[derive(Clone)] @@ -34,9 +32,6 @@ enum Transform { Inlay(Inlay), } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InlayId(usize); - #[derive(Debug, Clone)] pub struct Inlay { pub id: InlayId, @@ -316,7 +311,6 @@ impl InlayMap { snapshot: Mutex::new(snapshot.clone()), inlays_by_id: HashMap::default(), inlays: Vec::new(), - next_inlay_id: 0, }, snapshot, ) @@ -461,8 +455,8 @@ impl InlayMap { pub fn splice>( &mut self, to_remove: Vec, - to_insert: Vec<(Option, InlayProperties)>, - ) -> (InlaySnapshot, Vec, Vec) { + to_insert: Vec<(InlayId, InlayProperties)>, + ) -> (InlaySnapshot, Vec) { let snapshot = self.snapshot.lock(); let mut edits = BTreeSet::new(); @@ -474,16 +468,12 @@ impl InlayMap { } } - let mut new_inlay_ids = Vec::with_capacity(to_insert.len()); for (existing_id, properties) in to_insert { let inlay = Inlay { - id: existing_id.unwrap_or_else(|| InlayId(post_inc(&mut self.next_inlay_id))), + id: existing_id, position: properties.position, text: properties.text.into(), }; - if existing_id.is_none() { - new_inlay_ids.push(inlay.id); - } self.inlays_by_id.insert(inlay.id, inlay.clone()); match self .inlays @@ -508,7 +498,7 @@ impl InlayMap { let buffer_snapshot = snapshot.buffer.clone(); drop(snapshot); let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits); - (snapshot, edits, new_inlay_ids) + (snapshot, edits) } pub fn current_inlays(&self) -> impl Iterator { @@ -518,9 +508,11 @@ impl InlayMap { #[cfg(test)] pub(crate) fn randomly_mutate( &mut self, + next_inlay_id: &mut usize, rng: &mut rand::rngs::StdRng, ) -> (InlaySnapshot, Vec) { use rand::prelude::*; + use util::post_inc; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); @@ -541,7 +533,7 @@ impl InlayMap { text ); to_insert.push(( - None, + InlayId(post_inc(next_inlay_id)), InlayProperties { position: snapshot.buffer.anchor_at(position, bias), text, @@ -554,7 +546,7 @@ impl InlayMap { log::info!("removing inlays: {:?}", to_remove); drop(snapshot); - let (snapshot, edits, _) = self.splice(to_remove, to_insert); + let (snapshot, edits) = self.splice(to_remove, to_insert); (snapshot, edits) } } @@ -860,12 +852,13 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { #[cfg(test)] mod tests { use super::*; - use crate::MultiBuffer; + use crate::{InlayId, MultiBuffer}; use gpui::AppContext; use rand::prelude::*; use settings::SettingsStore; use std::env; use text::Patch; + use util::post_inc; #[gpui::test] fn test_basic_inlays(cx: &mut AppContext) { @@ -873,11 +866,12 @@ mod tests { let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abcdefghi"); + let mut next_inlay_id = 0; - let (inlay_snapshot, _, _) = inlay_map.splice( + let (inlay_snapshot, _) = inlay_map.splice( Vec::new(), vec![( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_after(3), text: "|123|", @@ -952,18 +946,18 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); - let (inlay_snapshot, _, _) = inlay_map.splice( + let (inlay_snapshot, _) = inlay_map.splice( Vec::new(), vec![ ( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(3), text: "|123|", }, ), ( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_after(3), text: "|456|", @@ -982,7 +976,7 @@ mod tests { assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); // The inlays can be manually removed. - let (inlay_snapshot, _, _) = inlay_map + let (inlay_snapshot, _) = inlay_map .splice::(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); } @@ -992,26 +986,27 @@ mod tests { let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); + let mut next_inlay_id = 0; - let (inlay_snapshot, _, _) = inlay_map.splice( + let (inlay_snapshot, _) = inlay_map.splice( Vec::new(), vec![ ( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(0), text: "|123|\n", }, ), ( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(4), text: "|456|", }, ), ( - None, + InlayId(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(7), text: "\n|567|\n", @@ -1044,6 +1039,7 @@ mod tests { MultiBuffer::build_random(&mut rng, cx) }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); + let mut next_inlay_id = 0; log::info!("buffer text: {:?}", buffer_snapshot.text()); let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); @@ -1054,7 +1050,7 @@ mod tests { let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=50 => { - let (snapshot, edits) = inlay_map.randomly_mutate(&mut rng); + let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); log::info!("mutated text: {:?}", snapshot.text()); inlay_edits = Patch::new(edits); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 021712cd4041fc620d68f0cd8ed332567f236ae3..9157caace496e72597e70ac039e7797672122dbc 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -708,7 +708,7 @@ mod tests { fold_map.randomly_mutate(&mut rng); let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut rng); + let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index a1f60920cd2b2068ec4ae08eb295b0b7a5ff8eb7..5197a2e0de49150f38e37e39ca7e42ba4050c026 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1119,6 +1119,7 @@ mod tests { ); log::info!("Wrapped text: {:?}", actual_text); + let mut next_inlay_id = 0; let mut edits = Vec::new(); for _i in 0..operations { log::info!("{} ==============================================", _i); @@ -1146,7 +1147,8 @@ mod tests { } } 40..=59 => { - let (inlay_snapshot, inlay_edits) = inlay_map.randomly_mutate(&mut rng); + let (inlay_snapshot, inlay_edits) = + inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 03e98407db7b457424424e31f437651aeedeb0c8..ed76ee3b297162492bc808cc230f8c3224cba940 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -185,6 +185,9 @@ pub struct GutterHover { pub hovered: bool, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InlayId(usize); + actions!( editor, [ @@ -541,6 +544,7 @@ pub struct Editor { link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, inlay_hint_cache: InlayHintCache, + next_inlay_id: usize, _subscriptions: Vec, } @@ -1339,6 +1343,7 @@ impl Editor { .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), completion_tasks: Default::default(), next_completion_id: 0, + next_inlay_id: 0, available_code_actions: Default::default(), code_actions_task: Default::default(), document_highlights_task: Default::default(), @@ -2664,7 +2669,7 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.append_inlays( + editor.inlay_hint_cache.append_hints( multi_buffer_handle, std::iter::once(updated_range_query), cx, @@ -2693,7 +2698,7 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.replace_inlays( + editor.inlay_hint_cache.replace_hints( multi_buffer_handle, replacement_queries.into_iter(), currently_shown_inlay_hints, @@ -2738,7 +2743,7 @@ impl Editor { fn splice_inlay_hints( &self, to_remove: Vec, - to_insert: Vec<(Option, Anchor, project::InlayHint)>, + to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, cx: &mut ViewContext, ) { let buffer = self.buffer.read(cx).read(cx); @@ -3514,23 +3519,19 @@ impl Editor { to_remove.push(suggestion.id); } + let suggestion_inlay_id = self.next_inlay_id(); let to_insert = vec![( - None, + suggestion_inlay_id, InlayProperties { position: cursor, text: text.clone(), }, )]; - let new_inlay_ids = self.display_map.update(cx, move |map, cx| { + self.display_map.update(cx, move |map, cx| { map.splice_inlays(to_remove, to_insert, cx) }); - assert_eq!( - new_inlay_ids.len(), - 1, - "Expecting only copilot suggestion id generated" - ); self.copilot_state.suggestion = Some(Inlay { - id: new_inlay_ids.into_iter().next().unwrap(), + id: suggestion_inlay_id, position: cursor, text, }); @@ -7687,6 +7688,10 @@ impl Editor { let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { return; }; cx.write_to_clipboard(ClipboardItem::new(lines)); } + + pub fn next_inlay_id(&mut self) -> InlayId { + InlayId(post_inc(&mut self.next_inlay_id)) + } } fn inlay_hint_query( diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 7f3124b5e83ab3d5c23a7c6a9caec1a9a626178b..b37545bcdf1df657f44d3537bb41f7014da157de 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,8 +1,7 @@ use std::ops::Range; use crate::{ - display_map::InlayId, editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, - MultiBuffer, + editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, }; use anyhow::Context; use clock::Global; @@ -12,6 +11,7 @@ use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; +use util::post_inc; #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { @@ -20,26 +20,39 @@ pub enum InlayRefreshReason { VisibleExcerptsChange, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct InlayHintCache { inlay_hints: HashMap, - inlays_in_buffers: HashMap>, + hints_in_buffers: HashMap>, allowed_hint_kinds: HashSet>, } -#[derive(Clone, Debug, Default)] -struct BufferInlays { +#[derive(Clone, Debug)] +struct BufferHints { buffer_version: Global, - cached_ranges: HashMap>>, - excerpt_inlays: HashMap>, + hints_per_excerpt: HashMap>, +} + +#[derive(Clone, Debug)] +struct ExcerptHints { + cached_excerpt_offsets: Vec>, + hints: Vec, +} + +impl Default for ExcerptHints { + fn default() -> Self { + Self { + cached_excerpt_offsets: Vec::new(), + hints: Vec::new(), + } + } } -impl BufferInlays { +impl BufferHints { fn new(buffer_version: Global) -> Self { Self { buffer_version, - excerpt_inlays: HashMap::default(), - cached_ranges: HashMap::default(), + hints_per_excerpt: HashMap::default(), } } } @@ -47,7 +60,7 @@ impl BufferInlays { #[derive(Debug, Default)] pub struct InlaySplice { pub to_remove: Vec, - pub to_insert: Vec<(Option, Anchor, InlayHint)>, + pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } pub struct InlayHintQuery { @@ -61,7 +74,7 @@ impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), - inlays_in_buffers: HashMap::default(), + hints_in_buffers: HashMap::default(), inlay_hints: HashMap::default(), } } @@ -117,26 +130,27 @@ impl InlayHintCache { } let reenabled_hints = self - .inlays_in_buffers + .hints_in_buffers .iter() .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| { let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?; - let not_considered_cached_inlays = cached_hints_per_excerpt - .excerpt_inlays + let not_considered_cached_hints = cached_hints_per_excerpt + .hints_per_excerpt .iter() - .filter_map(|(cached_excerpt_id, cached_hints)| { + .filter_map(|(cached_excerpt_id, cached_excerpt_hints)| { let considered_excerpt_hints = considered_hints_in_excerpts.get(&cached_excerpt_id)?; - let not_considered_cached_inlays = cached_hints + let not_considered_cached_hints = cached_excerpt_hints + .hints .iter() .filter(|(_, cached_hint_id)| { !considered_excerpt_hints.contains(cached_hint_id) }) .copied(); - Some(not_considered_cached_inlays) + Some(not_considered_cached_hints) }) .flatten(); - Some(not_considered_cached_inlays) + Some(not_considered_cached_hints) }) .flatten() .filter_map(|(cached_anchor, cached_inlay_id)| { @@ -148,11 +162,7 @@ impl InlayHintCache { }) .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind)) .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| { - ( - Some(cached_inlay_id), - cached_anchor, - reenabled_inlay.clone(), - ) + (cached_inlay_id, cached_anchor, reenabled_inlay.clone()) }); to_insert.extend(reenabled_hints); @@ -173,25 +183,25 @@ impl InlayHintCache { pub fn clear(&mut self) -> Vec { let ids_to_remove = self.inlay_hints.drain().map(|(id, _)| id).collect(); - self.inlays_in_buffers.clear(); + self.hints_in_buffers.clear(); ids_to_remove } - pub fn append_inlays( + pub fn append_hints( &mut self, multi_buffer: ModelHandle, ranges_to_add: impl Iterator, cx: &mut ViewContext, ) -> Task> { let queries = ranges_to_add.filter_map(|additive_query| { - let Some(cached_buffer_inlays) = self.inlays_in_buffers.get(&additive_query.buffer_id) + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&additive_query.buffer_id) else { return Some(vec![additive_query]) }; - if cached_buffer_inlays.buffer_version.changed_since(&additive_query.buffer_version) { + if cached_buffer_hints.buffer_version.changed_since(&additive_query.buffer_version) { return None } - let Some(excerpt_cached_ranges) = cached_buffer_inlays.cached_ranges.get(&additive_query.excerpt_id) + let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&additive_query.excerpt_id) else { return Some(vec![additive_query]) }; - let non_cached_ranges = missing_subranges(&excerpt_cached_ranges, &additive_query.excerpt_offset_query_range); + let non_cached_ranges = missing_subranges(&excerpt_hints.cached_excerpt_offsets, &additive_query.excerpt_offset_query_range); if non_cached_ranges.is_empty() { None } else { @@ -210,55 +220,59 @@ impl InlayHintCache { let new_hints = fetch_queries_task.await?; editor.update(&mut cx, |editor, cx| { let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); - let inlay_hint_cache = &mut editor.inlay_hint_cache; let mut to_insert = Vec::new(); for (new_buffer_id, new_hints_per_buffer) in new_hints { - let cached_buffer_inlays = inlay_hint_cache - .inlays_in_buffers + let cached_buffer_hints = editor + .inlay_hint_cache + .hints_in_buffers .entry(new_buffer_id) .or_insert_with(|| { - BufferInlays::new(new_hints_per_buffer.buffer_version.clone()) + BufferHints::new(new_hints_per_buffer.buffer_version.clone()) }); - if cached_buffer_inlays + if cached_buffer_hints .buffer_version .changed_since(&new_hints_per_buffer.buffer_version) { continue; } - for (new_excerpt_id, new_ranges) in new_hints_per_buffer.cached_ranges { - let cached_ranges = cached_buffer_inlays - .cached_ranges + for (new_excerpt_id, new_excerpt_hints) in + new_hints_per_buffer.hints_per_excerpt + { + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt .entry(new_excerpt_id) - .or_default(); - for new_range in new_ranges { - insert_and_merge_ranges(cached_ranges, &new_range) + .or_insert_with(|| ExcerptHints::default()); + for new_range in new_excerpt_hints.cached_excerpt_offsets { + insert_and_merge_ranges( + &mut cached_excerpt_hints.cached_excerpt_offsets, + &new_range, + ) } - } - for (new_excerpt_id, new_hints) in new_hints_per_buffer.excerpt_inlays { - let cached_inlays = cached_buffer_inlays - .excerpt_inlays - .entry(new_excerpt_id) - .or_default(); - for new_inlay_hint in new_hints { - let new_inlay_id = todo!("TODO kb"); + for new_inlay_hint in new_excerpt_hints.hints { let hint_anchor = multi_buffer_snapshot .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position); - match cached_inlays.binary_search_by(|probe| { - hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => { - cached_inlays.insert(ix, (hint_anchor, new_inlay_id)) - } - } - inlay_hint_cache + let insert_ix = + match cached_excerpt_hints.hints.binary_search_by(|probe| { + hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => ix, + }; + + let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + cached_excerpt_hints + .hints + .insert(insert_ix, (hint_anchor, new_inlay_id)); + editor + .inlay_hint_cache .inlay_hints .insert(new_inlay_id, new_inlay_hint.clone()); - if inlay_hint_cache + if editor + .inlay_hint_cache .allowed_hint_kinds .contains(&new_inlay_hint.kind) { - to_insert.push((Some(new_inlay_id), hint_anchor, new_inlay_hint)); + to_insert.push((new_inlay_id, hint_anchor, new_inlay_hint)); } } } @@ -272,7 +286,7 @@ impl InlayHintCache { }) } - pub fn replace_inlays( + pub fn replace_hints( &mut self, multi_buffer: ModelHandle, new_ranges: impl Iterator, @@ -401,7 +415,7 @@ fn fetch_queries<'a, 'b>( multi_buffer: ModelHandle, queries: impl Iterator, cx: &mut ViewContext<'a, 'b, Editor>, -) -> Task>>> { +) -> Task>>> { let mut inlay_fetch_tasks = Vec::new(); for query in queries { let task_multi_buffer = multi_buffer.clone(); @@ -434,33 +448,30 @@ fn fetch_queries<'a, 'b>( } cx.spawn(|editor, cx| async move { - let mut inlay_updates: HashMap> = HashMap::default(); + let mut inlay_updates: HashMap> = HashMap::default(); for task_result in futures::future::join_all(inlay_fetch_tasks).await { match task_result { - Ok((query, Some(response_inlays))) => { + Ok((query, Some(response_hints))) => { let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| { editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot()) })? else { continue; }; - let buffer_inlays = inlay_updates + let buffer_hints = inlay_updates .entry(query.buffer_id) - .or_insert_with(|| BufferInlays::new(query.buffer_version.clone())); - assert_eq!(buffer_inlays.buffer_version, query.buffer_version); - { - let cached_ranges = buffer_inlays - .cached_ranges - .entry(query.excerpt_id) - .or_default(); - insert_and_merge_ranges(cached_ranges, &query.excerpt_offset_query_range); - let excerpt_inlays = buffer_inlays - .excerpt_inlays - .entry(query.excerpt_id) - .or_default(); - for inlay in response_inlays { - match excerpt_inlays.binary_search_by(|probe| { - inlay.position.cmp(&probe.position, &buffer_snapshot) - }) { - Ok(ix) | Err(ix) => excerpt_inlays.insert(ix, inlay), - } + .or_insert_with(|| BufferHints::new(query.buffer_version.clone())); + if buffer_snapshot.version().changed_since(&buffer_hints.buffer_version) { + continue; + } + let cached_excerpt_hints = buffer_hints + .hints_per_excerpt + .entry(query.excerpt_id) + .or_default(); + insert_and_merge_ranges(&mut cached_excerpt_hints.cached_excerpt_offsets, &query.excerpt_offset_query_range); + let excerpt_hints = &mut cached_excerpt_hints.hints; + for inlay in response_hints { + match excerpt_hints.binary_search_by(|probe| { + inlay.position.cmp(&probe.position, &buffer_snapshot) + }) { + Ok(ix) | Err(ix) => excerpt_hints.insert(ix, inlay), } } } From 6368cf1a27b52f45baf540806f6e5829213715bd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 17 Jun 2023 23:54:03 +0300 Subject: [PATCH 112/169] Merge excerpt-related hint data, move next_inlay_id into Editor --- crates/editor/src/inlay_hint_cache.rs | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b37545bcdf1df657f44d3537bb41f7014da157de..4da2035af9ac09269c045a81a584425cd10320ba 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -73,7 +73,7 @@ pub struct InlayHintQuery { impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { - allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings), + allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), hints_in_buffers: HashMap::default(), inlay_hints: HashMap::default(), } @@ -83,10 +83,10 @@ impl InlayHintCache { &mut self, inlay_hint_settings: editor_settings::InlayHints, currently_visible_ranges: Vec<(ModelHandle, Range, ExcerptId)>, - mut currently_shown_inlay_hints: HashMap>>, + mut currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Option { - let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings); + let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if new_allowed_hint_kinds == self.allowed_hint_kinds { None } else { @@ -99,7 +99,7 @@ impl InlayHintCache { for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges { let visible_buffer = visible_buffer.read(cx); let visible_buffer_id = visible_buffer.remote_id(); - match currently_shown_inlay_hints.entry(visible_buffer_id) { + match currently_shown_hints.entry(visible_buffer_id) { hash_map::Entry::Occupied(mut o) => { let shown_hints_per_excerpt = o.get_mut(); for (_, shown_hint_id) in shown_hints_per_excerpt @@ -153,21 +153,21 @@ impl InlayHintCache { Some(not_considered_cached_hints) }) .flatten() - .filter_map(|(cached_anchor, cached_inlay_id)| { + .filter_map(|(cached_anchor, cached_hint_id)| { Some(( cached_anchor, - cached_inlay_id, - self.inlay_hints.get(&cached_inlay_id)?, + cached_hint_id, + self.inlay_hints.get(&cached_hint_id)?, )) }) - .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind)) - .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| { - (cached_inlay_id, cached_anchor, reenabled_inlay.clone()) + .filter(|(_, _, cached_hint)| self.allowed_hint_kinds.contains(&cached_hint.kind)) + .map(|(cached_anchor, cached_hint_id, reenabled_hint)| { + (cached_hint_id, cached_anchor, reenabled_hint.clone()) }); to_insert.extend(reenabled_hints); to_remove.extend( - currently_shown_inlay_hints + currently_shown_hints .into_iter() .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) .flat_map(|(_, excerpt_hints)| excerpt_hints) @@ -249,9 +249,9 @@ impl InlayHintCache { &new_range, ) } - for new_inlay_hint in new_excerpt_hints.hints { + for new_hint in new_excerpt_hints.hints { let hint_anchor = multi_buffer_snapshot - .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position); + .anchor_in_excerpt(new_excerpt_id, new_hint.position); let insert_ix = match cached_excerpt_hints.hints.binary_search_by(|probe| { hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) @@ -259,20 +259,20 @@ impl InlayHintCache { Ok(ix) | Err(ix) => ix, }; - let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); cached_excerpt_hints .hints - .insert(insert_ix, (hint_anchor, new_inlay_id)); + .insert(insert_ix, (hint_anchor, new_hint_id)); editor .inlay_hint_cache .inlay_hints - .insert(new_inlay_id, new_inlay_hint.clone()); + .insert(new_hint_id, new_hint.clone()); if editor .inlay_hint_cache .allowed_hint_kinds - .contains(&new_inlay_hint.kind) + .contains(&new_hint.kind) { - to_insert.push((new_inlay_id, hint_anchor, new_inlay_hint)); + to_insert.push((new_hint_id, hint_anchor, new_hint)); } } } @@ -290,7 +290,7 @@ impl InlayHintCache { &mut self, multi_buffer: ModelHandle, new_ranges: impl Iterator, - currently_shown_inlay_hints: HashMap>>, + currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); @@ -323,20 +323,20 @@ impl InlayHintCache { } } -fn allowed_inlay_hint_types( +fn allowed_hint_types( inlay_hint_settings: editor_settings::InlayHints, ) -> HashSet> { - let mut new_allowed_inlay_hint_types = HashSet::default(); + let mut new_allowed_hint_types = HashSet::default(); if inlay_hint_settings.show_type_hints { - new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Type)); + new_allowed_hint_types.insert(Some(InlayHintKind::Type)); } if inlay_hint_settings.show_parameter_hints { - new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Parameter)); + new_allowed_hint_types.insert(Some(InlayHintKind::Parameter)); } if inlay_hint_settings.show_other_hints { - new_allowed_inlay_hint_types.insert(None); + new_allowed_hint_types.insert(None); } - new_allowed_inlay_hint_types + new_allowed_hint_types } fn missing_subranges(cache: &[Range], input: &Range) -> Vec> { From ddcbc73bf019a203fafb65f95014af855383bc49 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 18 Jun 2023 01:18:22 +0300 Subject: [PATCH 113/169] Implement inlay hint replaces for conflict-less case --- crates/editor/src/inlay_hint_cache.rs | 239 ++++++++++++++++++++------ 1 file changed, 182 insertions(+), 57 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 4da2035af9ac09269c045a81a584425cd10320ba..85ca62840d5ae0a43d717684a2be4da246f48231 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -28,18 +28,18 @@ pub struct InlayHintCache { } #[derive(Clone, Debug)] -struct BufferHints { +struct BufferHints { buffer_version: Global, - hints_per_excerpt: HashMap>, + hints_per_excerpt: HashMap>, } #[derive(Clone, Debug)] -struct ExcerptHints { +struct ExcerptHints { cached_excerpt_offsets: Vec>, - hints: Vec, + hints: Vec, } -impl Default for ExcerptHints { +impl Default for ExcerptHints { fn default() -> Self { Self { cached_excerpt_offsets: Vec::new(), @@ -48,7 +48,7 @@ impl Default for ExcerptHints { } } -impl BufferHints { +impl BufferHints { fn new(buffer_version: Global) -> Self { Self { buffer_version, @@ -93,7 +93,6 @@ impl InlayHintCache { self.allowed_hint_kinds = new_allowed_hint_kinds; let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut considered_hints = HashMap::>>::default(); for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges { @@ -193,29 +192,10 @@ impl InlayHintCache { ranges_to_add: impl Iterator, cx: &mut ViewContext, ) -> Task> { - let queries = ranges_to_add.filter_map(|additive_query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&additive_query.buffer_id) - else { return Some(vec![additive_query]) }; - if cached_buffer_hints.buffer_version.changed_since(&additive_query.buffer_version) { - return None - } - let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&additive_query.excerpt_id) - else { return Some(vec![additive_query]) }; - let non_cached_ranges = missing_subranges(&excerpt_hints.cached_excerpt_offsets, &additive_query.excerpt_offset_query_range); - if non_cached_ranges.is_empty() { - None - } else { - Some(non_cached_ranges.into_iter().map(|non_cached_range| InlayHintQuery { - buffer_id: additive_query.buffer_id, - buffer_version: additive_query.buffer_version.clone(), - excerpt_id: additive_query.excerpt_id, - excerpt_offset_query_range: non_cached_range, - }).collect()) - } - }).flatten(); + let queries = filter_queries(ranges_to_add, &self.hints_in_buffers, false); let task_multi_buffer = multi_buffer.clone(); - let fetch_queries_task = fetch_queries(multi_buffer, queries, cx); + let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); cx.spawn(|editor, mut cx| async move { let new_hints = fetch_queries_task.await?; editor.update(&mut cx, |editor, cx| { @@ -289,40 +269,185 @@ impl InlayHintCache { pub fn replace_hints( &mut self, multi_buffer: ModelHandle, - new_ranges: impl Iterator, - currently_shown_hints: HashMap>>, + mut range_updates: impl Iterator, + mut currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - // let inlay_queries_per_buffer = inlay_queries.fold( - // HashMap::>::default(), - // |mut queries, new_query| { - // let mut buffer_queries = queries - // .entry(new_query.buffer_id) - // .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); - // assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); - // let queries = buffer_queries - // .excerpt_inlays - // .entry(new_query.excerpt_id) - // .or_default(); - // // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); - // // .push(new_query); - // // match queries - // // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) - // // { - // // Ok(ix) | Err(ix) => { - // // excerpt_hints.insert(ix, (inlay.position, inlay.id)); - // // } - // // } - // // queries - // todo!("TODO kb") - // }, - // ); - - todo!("TODO kb") + let conflicts_with_cache = range_updates.any(|update_query| { + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) + else { return false }; + if cached_buffer_hints + .buffer_version + .changed_since(&update_query.buffer_version) + { + false + } else if update_query + .buffer_version + .changed_since(&cached_buffer_hints.buffer_version) + { + true + } else { + cached_buffer_hints + .hints_per_excerpt + .contains_key(&update_query.excerpt_id) + } + }); + + let queries = filter_queries(range_updates, &self.hints_in_buffers, conflicts_with_cache); + let task_multi_buffer = multi_buffer.clone(); + let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + cx.spawn(|editor, mut cx| async move { + let new_hints = fetch_queries_task.await?; + editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); + for (new_buffer_id, new_hints_per_buffer) in new_hints { + let cached_buffer_hints = editor + .inlay_hint_cache + .hints_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferHints::new(new_hints_per_buffer.buffer_version.clone()) + }); + let mut shown_buffer_hints = currently_shown_hints + .remove(&new_buffer_id) + .unwrap_or_default(); + if cached_buffer_hints + .buffer_version + .changed_since(&new_hints_per_buffer.buffer_version) + { + continue; + } else { + cached_buffer_hints.buffer_version = new_hints_per_buffer.buffer_version; + } + + for (new_excerpt_id, new_hints_per_excerpt) in + new_hints_per_buffer.hints_per_excerpt + { + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt + .entry(new_excerpt_id) + .or_default(); + let shown_excerpt_hints = shown_buffer_hints + .remove(&new_excerpt_id) + .unwrap_or_default(); + + if conflicts_with_cache { + cached_excerpt_hints.cached_excerpt_offsets.clear(); + // TODO kb need to add such into to_delete and do not cause extra changes + // cached_excerpt_hints.hints.clear(); + // editor.inlay_hint_cache.inlay_hints.clear(); + todo!("TODO kb") + } else { + for new_range in new_hints_per_excerpt.cached_excerpt_offsets { + insert_and_merge_ranges( + &mut cached_excerpt_hints.cached_excerpt_offsets, + &new_range, + ) + } + for new_hint in new_hints_per_excerpt.hints { + let hint_anchor = multi_buffer_snapshot + .anchor_in_excerpt(new_excerpt_id, new_hint.position); + let insert_ix = + match cached_excerpt_hints.hints.binary_search_by(|probe| { + hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => ix, + }; + + let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + cached_excerpt_hints + .hints + .insert(insert_ix, (hint_anchor, new_hint_id)); + editor + .inlay_hint_cache + .inlay_hints + .insert(new_hint_id, new_hint.clone()); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + to_insert.push((new_hint_id, hint_anchor, new_hint)); + } + } + } + + if cached_excerpt_hints.hints.is_empty() { + cached_buffer_hints + .hints_per_excerpt + .remove(&new_excerpt_id); + } + } + + if shown_buffer_hints.is_empty() { + currently_shown_hints.remove(&new_buffer_id); + } + } + + to_remove.extend( + currently_shown_hints + .into_iter() + .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) + .flat_map(|(_, excerpt_hints)| excerpt_hints) + .map(|(_, hint_id)| hint_id), + ); + InlaySplice { + to_remove, + to_insert, + } + }) + }) } } +fn filter_queries( + queries: impl Iterator, + cached_hints: &HashMap>, + invalidate_cache: bool, +) -> Vec { + queries + .filter_map(|query| { + let Some(cached_buffer_hints) = cached_hints.get(&query.buffer_id) + else { return Some(vec![query]) }; + if cached_buffer_hints + .buffer_version + .changed_since(&query.buffer_version) + { + return None; + } + let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&query.excerpt_id) + else { return Some(vec![query]) }; + + if invalidate_cache { + Some(vec![query]) + } else { + let non_cached_ranges = missing_subranges( + &excerpt_hints.cached_excerpt_offsets, + &query.excerpt_offset_query_range, + ); + if non_cached_ranges.is_empty() { + None + } else { + Some( + non_cached_ranges + .into_iter() + .map(|non_cached_range| InlayHintQuery { + buffer_id: query.buffer_id, + buffer_version: query.buffer_version.clone(), + excerpt_id: query.excerpt_id, + excerpt_offset_query_range: non_cached_range, + }) + .collect(), + ) + } + } + }) + .flatten() + .collect() +} + fn allowed_hint_types( inlay_hint_settings: editor_settings::InlayHints, ) -> HashSet> { From debdc3603e8fa6f66691f740ee3180c5eef44660 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 18 Jun 2023 01:48:02 +0300 Subject: [PATCH 114/169] Finish rest of the inlay cache logic --- crates/editor/src/editor.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 194 ++++++++++++++++++++------ 2 files changed, 151 insertions(+), 45 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ed76ee3b297162492bc808cc230f8c3224cba940..c684821649675aafc9199e66dc0fdb85d6915502 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2700,7 +2700,7 @@ impl Editor { .update(&mut cx, |editor, cx| { editor.inlay_hint_cache.replace_hints( multi_buffer_handle, - replacement_queries.into_iter(), + replacement_queries, currently_shown_inlay_hints, cx, ) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 85ca62840d5ae0a43d717684a2be4da246f48231..72eb241e5a63581c3a8b4438a86954dc9c5ede82 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,4 +1,4 @@ -use std::ops::Range; +use std::{cmp, ops::Range}; use crate::{ editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, @@ -63,6 +63,7 @@ pub struct InlaySplice { pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, } +#[derive(Debug)] pub struct InlayHintQuery { pub buffer_id: u64, pub buffer_version: Global, @@ -186,6 +187,7 @@ impl InlayHintCache { ids_to_remove } + // TODO kb deduplicate into replace_hints? pub fn append_hints( &mut self, multi_buffer: ModelHandle, @@ -230,29 +232,44 @@ impl InlayHintCache { ) } for new_hint in new_excerpt_hints.hints { - let hint_anchor = multi_buffer_snapshot + let new_hint_anchor = multi_buffer_snapshot .anchor_in_excerpt(new_excerpt_id, new_hint.position); let insert_ix = match cached_excerpt_hints.hints.binary_search_by(|probe| { - hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) }) { - Ok(ix) | Err(ix) => ix, + Ok(ix) => { + let (_, cached_inlay_id) = cached_excerpt_hints.hints[ix]; + let cached_hint = editor + .inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .unwrap(); + if cached_hint == &new_hint { + None + } else { + Some(ix) + } + } + Err(ix) => Some(ix), }; - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); - cached_excerpt_hints - .hints - .insert(insert_ix, (hint_anchor, new_hint_id)); - editor - .inlay_hint_cache - .inlay_hints - .insert(new_hint_id, new_hint.clone()); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - to_insert.push((new_hint_id, hint_anchor, new_hint)); + if let Some(insert_ix) = insert_ix { + let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + cached_excerpt_hints + .hints + .insert(insert_ix, (new_hint_anchor, new_hint_id)); + editor + .inlay_hint_cache + .inlay_hints + .insert(new_hint_id, new_hint.clone()); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + to_insert.push((new_hint_id, new_hint_anchor, new_hint)); + } } } } @@ -269,11 +286,11 @@ impl InlayHintCache { pub fn replace_hints( &mut self, multi_buffer: ModelHandle, - mut range_updates: impl Iterator, + range_updates: Vec, mut currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { - let conflicts_with_cache = range_updates.any(|update_query| { + let conflicts_with_cache = range_updates.iter().any(|update_query| { let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) else { return false }; if cached_buffer_hints @@ -293,7 +310,11 @@ impl InlayHintCache { } }); - let queries = filter_queries(range_updates, &self.hints_in_buffers, conflicts_with_cache); + let queries = filter_queries( + range_updates.into_iter(), + &self.hints_in_buffers, + conflicts_with_cache, + ); let task_multi_buffer = multi_buffer.clone(); let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); let mut to_remove = Vec::new(); @@ -329,37 +350,115 @@ impl InlayHintCache { .hints_per_excerpt .entry(new_excerpt_id) .or_default(); - let shown_excerpt_hints = shown_buffer_hints + let mut shown_excerpt_hints = shown_buffer_hints .remove(&new_excerpt_id) - .unwrap_or_default(); - + .unwrap_or_default() + .into_iter() + .fuse() + .peekable(); if conflicts_with_cache { cached_excerpt_hints.cached_excerpt_offsets.clear(); - // TODO kb need to add such into to_delete and do not cause extra changes - // cached_excerpt_hints.hints.clear(); - // editor.inlay_hint_cache.inlay_hints.clear(); - todo!("TODO kb") - } else { - for new_range in new_hints_per_excerpt.cached_excerpt_offsets { - insert_and_merge_ranges( - &mut cached_excerpt_hints.cached_excerpt_offsets, - &new_range, - ) - } - for new_hint in new_hints_per_excerpt.hints { - let hint_anchor = multi_buffer_snapshot - .anchor_in_excerpt(new_excerpt_id, new_hint.position); + cached_excerpt_hints.hints.clear(); + } + + for new_hint in new_hints_per_excerpt.hints { + let new_hint_anchor = multi_buffer_snapshot + .anchor_in_excerpt(new_excerpt_id, new_hint.position); + + let insert_ix = if conflicts_with_cache { + let mut no_matching_inlay_displayed = true; + loop { + match shown_excerpt_hints.peek() { + Some((shown_anchor, shown_id)) => { + match shown_anchor + .cmp(&new_hint_anchor, &multi_buffer_snapshot) + { + cmp::Ordering::Less => { + editor + .inlay_hint_cache + .inlay_hints + .remove(shown_id); + to_remove.push(*shown_id); + shown_excerpt_hints.next(); + } + cmp::Ordering::Equal => { + match editor + .inlay_hint_cache + .inlay_hints + .get(shown_id) + { + Some(cached_hint) + if cached_hint == &new_hint => + { + no_matching_inlay_displayed = false; + } + _ => to_remove.push(*shown_id), + } + shown_excerpt_hints.next(); + break; + } + cmp::Ordering::Greater => break, + } + } + None => break, + } + } + + if no_matching_inlay_displayed { + let insert_ix = + match cached_excerpt_hints.hints.binary_search_by(|probe| { + new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) => { + let (_, cached_inlay_id) = + cached_excerpt_hints.hints[ix]; + let cached_hint = editor + .inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .unwrap(); + if cached_hint == &new_hint { + None + } else { + Some(ix) + } + } + Err(ix) => Some(ix), + }; + insert_ix + } else { + None + } + } else { let insert_ix = match cached_excerpt_hints.hints.binary_search_by(|probe| { - hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) }) { - Ok(ix) | Err(ix) => ix, + Ok(ix) => { + let (_, cached_inlay_id) = + cached_excerpt_hints.hints[ix]; + let cached_hint = editor + .inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .unwrap(); + if cached_hint == &new_hint { + None + } else { + Some(ix) + } + } + Err(ix) => Some(ix), }; + insert_ix + }; + + if let Some(insert_ix) = insert_ix { let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); cached_excerpt_hints .hints - .insert(insert_ix, (hint_anchor, new_hint_id)); + .insert(insert_ix, (new_hint_anchor, new_hint_id)); editor .inlay_hint_cache .inlay_hints @@ -369,11 +468,18 @@ impl InlayHintCache { .allowed_hint_kinds .contains(&new_hint.kind) { - to_insert.push((new_hint_id, hint_anchor, new_hint)); + to_insert.push((new_hint_id, new_hint_anchor, new_hint)); } } } + for new_range in new_hints_per_excerpt.cached_excerpt_offsets { + insert_and_merge_ranges( + &mut cached_excerpt_hints.cached_excerpt_offsets, + &new_range, + ) + } + if cached_excerpt_hints.hints.is_empty() { cached_buffer_hints .hints_per_excerpt @@ -521,7 +627,7 @@ fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range 0 && cache[index - 1].end >= new_range.start { // Merge with the previous range - cache[index - 1].end = std::cmp::max(cache[index - 1].end, new_range.end); + cache[index - 1].end = cmp::max(cache[index - 1].end, new_range.end); } else { // Insert the new range, as it doesn't overlap with the previous range cache.insert(index, new_range.clone()); @@ -530,7 +636,7 @@ fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range= cache[i + 1].start { - cache[i].end = std::cmp::max(cache[i].end, cache[i + 1].end); + cache[i].end = cmp::max(cache[i].end, cache[i + 1].end); cache.remove(i + 1); i += 1; } From 58343563ba256f1e509dcc788f3a303fe46671eb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 18 Jun 2023 14:16:16 +0300 Subject: [PATCH 115/169] Fix hint querying bugs --- crates/editor/src/editor.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c684821649675aafc9199e66dc0fdb85d6915502..dd4b4167a0577ee5e439f89ab7a0d3527a8a8b27 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2749,7 +2749,7 @@ impl Editor { let buffer = self.buffer.read(cx).read(cx); let new_inlays = to_insert .into_iter() - .map(|(id, hint_anchor, hint)| { + .map(|(id, position, hint)| { let mut text = hint.text(); // TODO kb styling instead? if hint.padding_right { @@ -2758,14 +2758,7 @@ impl Editor { if hint.padding_left { text.insert(0, ' '); } - - ( - id, - InlayProperties { - position: hint_anchor.bias_left(&buffer), - text, - }, - ) + (id, InlayProperties { position, text }) }) .collect(); drop(buffer); @@ -7355,7 +7348,7 @@ impl Editor { } multi_buffer::Event::DirtyChanged => { cx.emit(Event::DirtyChanged); - true + false } multi_buffer::Event::Saved => { cx.emit(Event::Saved); @@ -7363,11 +7356,11 @@ impl Editor { } multi_buffer::Event::FileHandleChanged => { cx.emit(Event::TitleChanged); - true + false } multi_buffer::Event::Reloaded => { cx.emit(Event::TitleChanged); - true + false } multi_buffer::Event::DiffBaseChanged => { cx.emit(Event::DiffBaseChanged); From 7ac1885449de375a7b7435b37ec5ede0e2924976 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 18 Jun 2023 15:23:53 +0300 Subject: [PATCH 116/169] Properly refresh hints on editor open --- crates/editor/src/editor.rs | 1 - crates/editor/src/element.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 14 +++++++++++--- crates/editor/src/scroll.rs | 8 ++++++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dd4b4167a0577ee5e439f89ab7a0d3527a8a8b27..b834ac6aca64ff66dc3091e3594efcb63cc22a82 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1385,7 +1385,6 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); this } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6525e7fc222843fdfa04a94317f4a2a95f6dadcf..7936ed76cb85da078e61493b812c33d83a9a72b4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1921,7 +1921,7 @@ impl Element for EditorElement { let em_advance = style.text.em_advance(cx.font_cache()); let overscroll = vec2f(em_width, 0.); let snapshot = { - editor.set_visible_line_count(size.y() / line_height); + editor.set_visible_line_count(size.y() / line_height, cx); let editor_width = text_width - gutter_margin - overscroll.x() - em_width; let wrap_width = match editor.soft_wrap_mode(cx) { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 72eb241e5a63581c3a8b4438a86954dc9c5ede82..a89d6da4dfbc88f22901c4c5048c884f230d6de8 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -331,18 +331,19 @@ impl InlayHintCache { .or_insert_with(|| { BufferHints::new(new_hints_per_buffer.buffer_version.clone()) }); - let mut shown_buffer_hints = currently_shown_hints - .remove(&new_buffer_id) - .unwrap_or_default(); + if cached_buffer_hints .buffer_version .changed_since(&new_hints_per_buffer.buffer_version) { + currently_shown_hints.remove(&new_buffer_id); continue; } else { cached_buffer_hints.buffer_version = new_hints_per_buffer.buffer_version; } + let shown_buffer_hints = + currently_shown_hints.entry(new_buffer_id).or_default(); for (new_excerpt_id, new_hints_per_excerpt) in new_hints_per_buffer.hints_per_excerpt { @@ -489,6 +490,13 @@ impl InlayHintCache { if shown_buffer_hints.is_empty() { currently_shown_hints.remove(&new_buffer_id); + } else { + to_remove.extend( + shown_buffer_hints + .iter() + .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt.iter()) + .map(|(_, hint_id)| *hint_id), + ); } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index b5343359b58fe7c346a0c35cb48d09460aaba560..62986b69368c5bce40844ba4543dac17dced6f63 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -295,8 +295,12 @@ impl Editor { self.scroll_manager.visible_line_count } - pub(crate) fn set_visible_line_count(&mut self, lines: f32) { - self.scroll_manager.visible_line_count = Some(lines) + pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { + let had_no_visibles = self.scroll_manager.visible_line_count.is_none(); + self.scroll_manager.visible_line_count = Some(lines); + if had_no_visibles { + self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); + } } pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { From 70a45fc80097203a9c58ab79d9d7f4c29e16bd66 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 18 Jun 2023 23:14:48 +0300 Subject: [PATCH 117/169] Fix cache incremental updates --- crates/editor/src/editor.rs | 9 +- crates/editor/src/inlay_hint_cache.rs | 395 +++++++++----------------- 2 files changed, 145 insertions(+), 259 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b834ac6aca64ff66dc3091e3594efcb63cc22a82..a7b9d9fb1728e676728cf48db144d2ea85abcbf8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2668,9 +2668,11 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.append_hints( + editor.inlay_hint_cache.update_hints( multi_buffer_handle, - std::iter::once(updated_range_query), + vec![updated_range_query], + currently_shown_inlay_hints, + false, cx, ) })? @@ -2697,10 +2699,11 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.replace_hints( + editor.inlay_hint_cache.update_hints( multi_buffer_handle, replacement_queries, currently_shown_inlay_hints, + true, cx, ) })? diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index a89d6da4dfbc88f22901c4c5048c884f230d6de8..5dc4d04f41f679a81b1d1b78185eea3ceb777f47 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -84,9 +84,10 @@ impl InlayHintCache { &mut self, inlay_hint_settings: editor_settings::InlayHints, currently_visible_ranges: Vec<(ModelHandle, Range, ExcerptId)>, - mut currently_shown_hints: HashMap>>, + currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Option { + let mut shown_hints_to_clean = currently_shown_hints; let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if new_allowed_hint_kinds == self.allowed_hint_kinds { None @@ -99,7 +100,7 @@ impl InlayHintCache { for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges { let visible_buffer = visible_buffer.read(cx); let visible_buffer_id = visible_buffer.remote_id(); - match currently_shown_hints.entry(visible_buffer_id) { + match shown_hints_to_clean.entry(visible_buffer_id) { hash_map::Entry::Occupied(mut o) => { let shown_hints_per_excerpt = o.get_mut(); for (_, shown_hint_id) in shown_hints_per_excerpt @@ -167,7 +168,7 @@ impl InlayHintCache { to_insert.extend(reenabled_hints); to_remove.extend( - currently_shown_hints + shown_hints_to_clean .into_iter() .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) .flat_map(|(_, excerpt_hints)| excerpt_hints) @@ -187,128 +188,34 @@ impl InlayHintCache { ids_to_remove } - // TODO kb deduplicate into replace_hints? - pub fn append_hints( - &mut self, - multi_buffer: ModelHandle, - ranges_to_add: impl Iterator, - cx: &mut ViewContext, - ) -> Task> { - let queries = filter_queries(ranges_to_add, &self.hints_in_buffers, false); - - let task_multi_buffer = multi_buffer.clone(); - let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); - cx.spawn(|editor, mut cx| async move { - let new_hints = fetch_queries_task.await?; - editor.update(&mut cx, |editor, cx| { - let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); - let mut to_insert = Vec::new(); - for (new_buffer_id, new_hints_per_buffer) in new_hints { - let cached_buffer_hints = editor - .inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new(new_hints_per_buffer.buffer_version.clone()) - }); - if cached_buffer_hints - .buffer_version - .changed_since(&new_hints_per_buffer.buffer_version) - { - continue; - } - - for (new_excerpt_id, new_excerpt_hints) in - new_hints_per_buffer.hints_per_excerpt - { - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(new_excerpt_id) - .or_insert_with(|| ExcerptHints::default()); - for new_range in new_excerpt_hints.cached_excerpt_offsets { - insert_and_merge_ranges( - &mut cached_excerpt_hints.cached_excerpt_offsets, - &new_range, - ) - } - for new_hint in new_excerpt_hints.hints { - let new_hint_anchor = multi_buffer_snapshot - .anchor_in_excerpt(new_excerpt_id, new_hint.position); - let insert_ix = - match cached_excerpt_hints.hints.binary_search_by(|probe| { - new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) => { - let (_, cached_inlay_id) = cached_excerpt_hints.hints[ix]; - let cached_hint = editor - .inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .unwrap(); - if cached_hint == &new_hint { - None - } else { - Some(ix) - } - } - Err(ix) => Some(ix), - }; - - if let Some(insert_ix) = insert_ix { - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); - cached_excerpt_hints - .hints - .insert(insert_ix, (new_hint_anchor, new_hint_id)); - editor - .inlay_hint_cache - .inlay_hints - .insert(new_hint_id, new_hint.clone()); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - to_insert.push((new_hint_id, new_hint_anchor, new_hint)); - } - } - } - } - } - - InlaySplice { - to_remove: Vec::new(), - to_insert, - } - }) - }) - } - - pub fn replace_hints( + pub fn update_hints( &mut self, multi_buffer: ModelHandle, range_updates: Vec, - mut currently_shown_hints: HashMap>>, + currently_shown_hints: HashMap>>, + conflicts_invalidate_cache: bool, cx: &mut ViewContext, ) -> Task> { - let conflicts_with_cache = range_updates.iter().any(|update_query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) + let conflicts_with_cache = conflicts_invalidate_cache + && range_updates.iter().any(|update_query| { + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) else { return false }; - if cached_buffer_hints - .buffer_version - .changed_since(&update_query.buffer_version) - { - false - } else if update_query - .buffer_version - .changed_since(&cached_buffer_hints.buffer_version) - { - true - } else { - cached_buffer_hints - .hints_per_excerpt - .contains_key(&update_query.excerpt_id) - } - }); + if cached_buffer_hints + .buffer_version + .changed_since(&update_query.buffer_version) + { + false + } else if update_query + .buffer_version + .changed_since(&cached_buffer_hints.buffer_version) + { + true + } else { + cached_buffer_hints + .hints_per_excerpt + .contains_key(&update_query.excerpt_id) + } + }); let queries = filter_queries( range_updates.into_iter(), @@ -319,8 +226,12 @@ impl InlayHintCache { let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); + let mut cache_hints_to_persist: HashMap< + u64, + (Global, HashMap>), + > = HashMap::default(); cx.spawn(|editor, mut cx| async move { - let new_hints = fetch_queries_task.await?; + let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; editor.update(&mut cx, |editor, cx| { let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); for (new_buffer_id, new_hints_per_buffer) in new_hints { @@ -332,181 +243,152 @@ impl InlayHintCache { BufferHints::new(new_hints_per_buffer.buffer_version.clone()) }); + let buffer_cache_hints_to_persist = + cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); if cached_buffer_hints .buffer_version .changed_since(&new_hints_per_buffer.buffer_version) { - currently_shown_hints.remove(&new_buffer_id); + buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; + buffer_cache_hints_to_persist.1.extend( + cached_buffer_hints.hints_per_excerpt.iter().map( + |(excerpt_id, excerpt_hints)| { + ( + *excerpt_id, + excerpt_hints.hints.iter().map(|(_, id)| *id).collect(), + ) + }, + ), + ); continue; - } else { - cached_buffer_hints.buffer_version = new_hints_per_buffer.buffer_version; } - let shown_buffer_hints = - currently_shown_hints.entry(new_buffer_id).or_default(); + let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); for (new_excerpt_id, new_hints_per_excerpt) in new_hints_per_buffer.hints_per_excerpt { + let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 + .entry(new_excerpt_id) + .or_default(); let cached_excerpt_hints = cached_buffer_hints .hints_per_excerpt .entry(new_excerpt_id) .or_default(); - let mut shown_excerpt_hints = shown_buffer_hints - .remove(&new_excerpt_id) - .unwrap_or_default() - .into_iter() - .fuse() - .peekable(); - if conflicts_with_cache { - cached_excerpt_hints.cached_excerpt_offsets.clear(); - cached_excerpt_hints.hints.clear(); - } - + let empty_shown_excerpt_hints = Vec::new(); + let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); for new_hint in new_hints_per_excerpt.hints { let new_hint_anchor = multi_buffer_snapshot .anchor_in_excerpt(new_excerpt_id, new_hint.position); - - let insert_ix = if conflicts_with_cache { - let mut no_matching_inlay_displayed = true; - loop { - match shown_excerpt_hints.peek() { - Some((shown_anchor, shown_id)) => { - match shown_anchor - .cmp(&new_hint_anchor, &multi_buffer_snapshot) - { - cmp::Ordering::Less => { - editor - .inlay_hint_cache - .inlay_hints - .remove(shown_id); - to_remove.push(*shown_id); - shown_excerpt_hints.next(); - } - cmp::Ordering::Equal => { - match editor - .inlay_hint_cache - .inlay_hints - .get(shown_id) - { - Some(cached_hint) - if cached_hint == &new_hint => - { - no_matching_inlay_displayed = false; - } - _ => to_remove.push(*shown_id), - } - shown_excerpt_hints.next(); - break; - } - cmp::Ordering::Greater => break, - } - } - None => break, + let cache_insert_ix = match cached_excerpt_hints.hints.binary_search_by(|probe| { + new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) => { + let (_, cached_inlay_id) = + cached_excerpt_hints.hints[ix]; + let cache_hit = editor + .inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint) + .is_some(); + if cache_hit { + excerpt_cache_hints_to_persist + .insert(cached_inlay_id); + None + } else { + Some(ix) } } + Err(ix) => Some(ix), + }; - if no_matching_inlay_displayed { - let insert_ix = - match cached_excerpt_hints.hints.binary_search_by(|probe| { - new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) => { - let (_, cached_inlay_id) = - cached_excerpt_hints.hints[ix]; - let cached_hint = editor - .inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .unwrap(); - if cached_hint == &new_hint { - None - } else { - Some(ix) - } - } - Err(ix) => Some(ix), - }; - insert_ix - } else { - None - } - } else { - let insert_ix = - match cached_excerpt_hints.hints.binary_search_by(|probe| { - new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) => { - let (_, cached_inlay_id) = - cached_excerpt_hints.hints[ix]; - let cached_hint = editor - .inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .unwrap(); - if cached_hint == &new_hint { - None - } else { - Some(ix) - } - } - Err(ix) => Some(ix), - }; - - insert_ix + let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { + probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) + }) { + Ok(ix) => {{ + let (_, shown_inlay_id) = shown_excerpt_hints[ix]; + let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint).is_some(); + if shown_hint_found { + Some(shown_inlay_id) + } else { + None + } + }}, + Err(_) => None, }; - if let Some(insert_ix) = insert_ix { - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if let Some(insert_ix) = cache_insert_ix { + let hint_id = match shown_inlay_id { + Some(shown_inlay_id) => shown_inlay_id, + None => { + let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) + { + to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); + } + new_hint_id + } + }; + excerpt_cache_hints_to_persist.insert(hint_id); cached_excerpt_hints .hints - .insert(insert_ix, (new_hint_anchor, new_hint_id)); + .insert(insert_ix, (new_hint_anchor, hint_id)); editor .inlay_hint_cache .inlay_hints - .insert(new_hint_id, new_hint.clone()); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - to_insert.push((new_hint_id, new_hint_anchor, new_hint)); - } + .insert(hint_id, new_hint); } } + if conflicts_with_cache { + cached_excerpt_hints.cached_excerpt_offsets.clear(); + } for new_range in new_hints_per_excerpt.cached_excerpt_offsets { insert_and_merge_ranges( &mut cached_excerpt_hints.cached_excerpt_offsets, &new_range, ) } + } + } - if cached_excerpt_hints.hints.is_empty() { - cached_buffer_hints - .hints_per_excerpt - .remove(&new_excerpt_id); + if conflicts_with_cache { + for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { + match cache_hints_to_persist.get(&shown_buffer_id) { + Some(cached_buffer_hints) => { + for (persisted_id, cached_hints) in &cached_buffer_hints.1 { + shown_hints_to_clean.entry(*persisted_id).or_default() + .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); + } + }, + None => {}, } + to_remove.extend(shown_hints_to_clean.into_iter() + .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); } - if shown_buffer_hints.is_empty() { - currently_shown_hints.remove(&new_buffer_id); - } else { - to_remove.extend( - shown_buffer_hints - .iter() - .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt.iter()) - .map(|(_, hint_id)| *hint_id), - ); - } + editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { + let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; + buffer_hints.buffer_version = buffer_hints_to_persist.0; + buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { + let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; + excerpt_hints.hints.retain(|(_, hint_id)| { + let retain = excerpt_hints_to_persist.contains(hint_id); + if !retain { + editor + .inlay_hint_cache + .inlay_hints + .remove(hint_id); + } + retain + }); + !excerpt_hints.hints.is_empty() + }); + !buffer_hints.hints_per_excerpt.is_empty() + }); } - to_remove.extend( - currently_shown_hints - .into_iter() - .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) - .flat_map(|(_, excerpt_hints)| excerpt_hints) - .map(|(_, hint_id)| hint_id), - ); InlaySplice { to_remove, to_insert, @@ -521,6 +403,7 @@ fn filter_queries( cached_hints: &HashMap>, invalidate_cache: bool, ) -> Vec { + // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed queries .filter_map(|query| { let Some(cached_buffer_hints) = cached_hints.get(&query.buffer_id) @@ -650,10 +533,10 @@ fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range( +fn fetch_queries( multi_buffer: ModelHandle, queries: impl Iterator, - cx: &mut ViewContext<'a, 'b, Editor>, + cx: &mut ViewContext<'_, '_, Editor>, ) -> Task>>> { let mut inlay_fetch_tasks = Vec::new(); for query in queries { @@ -673,7 +556,7 @@ fn fetch_queries<'a, 'b>( }) }) }) - .context("inlays fecth task spawn")?; + .context("inlays fetch task spawn")?; Ok(( query, match task { From 3b9a2e32616b905d513367b376ec655d3e87bc16 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 19 Jun 2023 16:48:58 +0300 Subject: [PATCH 118/169] Do not track editor ranges in InlayHintCache --- crates/editor/src/editor.rs | 96 +----- crates/editor/src/element.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 413 +++++++++----------------- crates/editor/src/scroll.rs | 13 +- 4 files changed, 161 insertions(+), 363 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a7b9d9fb1728e676728cf48db144d2ea85abcbf8..8aa5e30742aa66d3da6bc73f2b0cd743423b7124 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1385,6 +1385,7 @@ impl Editor { } this.report_editor_event("open", None, cx); + this.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); this } @@ -2597,19 +2598,13 @@ impl Editor { } fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { - if self.mode != EditorMode::Full { - return; - } - - if !settings::get::(cx).inlay_hints.enabled { - let to_remove = self.inlay_hint_cache.clear(); - self.splice_inlay_hints(to_remove, Vec::new(), cx); + if self.mode != EditorMode::Full || !settings::get::(cx).inlay_hints.enabled + { return; } let multi_buffer_handle = self.buffer().clone(); let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx); - let currently_visible_ranges = self.excerpt_visible_offsets(&multi_buffer_handle, cx); let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold( HashMap::>>::default(), |mut current_hints, inlay| { @@ -2636,61 +2631,25 @@ impl Editor { to_remove, to_insert, }) = self.inlay_hint_cache.apply_settings( + &multi_buffer_handle, new_settings, - currently_visible_ranges, currently_shown_inlay_hints, cx, ) { self.splice_inlay_hints(to_remove, to_insert, cx); } } - InlayRefreshReason::Scroll(scrolled_to) => { - if let Some(updated_range_query) = currently_visible_ranges.iter().find_map( - |(buffer, excerpt_visible_offset_range, excerpt_id)| { - let buffer_id = scrolled_to.anchor.buffer_id?; - if buffer_id == buffer.read(cx).remote_id() - && &scrolled_to.anchor.excerpt_id == excerpt_id - { - Some(inlay_hint_query( - buffer, - *excerpt_id, - excerpt_visible_offset_range, - cx, - )) - } else { - None - } - }, - ) { - cx.spawn(|editor, mut cx| async move { - let InlaySplice { - to_remove, - to_insert, - } = editor - .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.update_hints( - multi_buffer_handle, - vec![updated_range_query], - currently_shown_inlay_hints, - false, - cx, - ) - })? - .await - .context("inlay cache hint fetch")?; - - editor.update(&mut cx, |editor, cx| { - editor.splice_inlay_hints(to_remove, to_insert, cx) - }) - }) - .detach_and_log_err(cx); - } - } InlayRefreshReason::VisibleExcerptsChange => { - let replacement_queries = currently_visible_ranges - .iter() - .map(|(buffer, excerpt_visible_offset_range, excerpt_id)| { - inlay_hint_query(buffer, *excerpt_id, excerpt_visible_offset_range, cx) + let replacement_queries = self + .excerpt_visible_offsets(&multi_buffer_handle, cx) + .into_iter() + .map(|(buffer, _, excerpt_id)| { + let buffer = buffer.read(cx); + InlayHintQuery { + buffer_id: buffer.remote_id(), + buffer_version: buffer.version.clone(), + excerpt_id, + } }) .collect::>(); cx.spawn(|editor, mut cx| async move { @@ -2703,7 +2662,6 @@ impl Editor { multi_buffer_handle, replacement_queries, currently_shown_inlay_hints, - true, cx, ) })? @@ -7689,32 +7647,6 @@ impl Editor { } } -fn inlay_hint_query( - buffer: &ModelHandle, - excerpt_id: ExcerptId, - excerpt_visible_offset_range: &Range, - cx: &mut ViewContext<'_, '_, Editor>, -) -> InlayHintQuery { - let buffer = buffer.read(cx); - let max_buffer_len = buffer.len(); - let visible_offset_range_len = excerpt_visible_offset_range.len(); - - let query_range_start = excerpt_visible_offset_range - .start - .saturating_sub(visible_offset_range_len); - let query_range_end = max_buffer_len.min( - excerpt_visible_offset_range - .end - .saturating_add(visible_offset_range_len), - ); - InlayHintQuery { - buffer_id: buffer.remote_id(), - buffer_version: buffer.version().clone(), - excerpt_id, - excerpt_offset_query_range: query_range_start..query_range_end, - } -} - fn consume_contiguous_rows( contiguous_row_selections: &mut Vec>, selection: &Selection, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7936ed76cb85da078e61493b812c33d83a9a72b4..6525e7fc222843fdfa04a94317f4a2a95f6dadcf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1921,7 +1921,7 @@ impl Element for EditorElement { let em_advance = style.text.em_advance(cx.font_cache()); let overscroll = vec2f(em_width, 0.); let snapshot = { - editor.set_visible_line_count(size.y() / line_height, cx); + editor.set_visible_line_count(size.y() / line_height); let editor_width = text_width - gutter_margin - overscroll.x() - em_width; let wrap_width = match editor.soft_wrap_mode(cx) { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 5dc4d04f41f679a81b1d1b78185eea3ceb777f47..7419a8e4847181cf4d6f074e3f9e71958eee8892 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,22 +1,18 @@ -use std::{cmp, ops::Range}; +use std::cmp; -use crate::{ - editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, -}; +use crate::{editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer}; use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; -use language::Buffer; use log::error; use project::{InlayHint, InlayHintKind}; -use collections::{hash_map, HashMap, HashSet}; +use collections::{HashMap, HashSet}; use util::post_inc; #[derive(Debug, Copy, Clone)] pub enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), - Scroll(ScrollAnchor), VisibleExcerptsChange, } @@ -30,22 +26,7 @@ pub struct InlayHintCache { #[derive(Clone, Debug)] struct BufferHints { buffer_version: Global, - hints_per_excerpt: HashMap>, -} - -#[derive(Clone, Debug)] -struct ExcerptHints { - cached_excerpt_offsets: Vec>, - hints: Vec, -} - -impl Default for ExcerptHints { - fn default() -> Self { - Self { - cached_excerpt_offsets: Vec::new(), - hints: Vec::new(), - } - } + hints_per_excerpt: HashMap>, } impl BufferHints { @@ -68,7 +49,6 @@ pub struct InlayHintQuery { pub buffer_id: u64, pub buffer_version: Global, pub excerpt_id: ExcerptId, - pub excerpt_offset_query_range: Range, } impl InlayHintCache { @@ -82,99 +62,108 @@ impl InlayHintCache { pub fn apply_settings( &mut self, + multi_buffer: &ModelHandle, inlay_hint_settings: editor_settings::InlayHints, - currently_visible_ranges: Vec<(ModelHandle, Range, ExcerptId)>, currently_shown_hints: HashMap>>, cx: &mut ViewContext, ) -> Option { - let mut shown_hints_to_clean = currently_shown_hints; + if !inlay_hint_settings.enabled { + self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); + if self.inlay_hints.is_empty() { + return None; + } else { + let to_remove = self.inlay_hints.keys().copied().collect(); + self.inlay_hints.clear(); + self.hints_in_buffers.clear(); + return Some(InlaySplice { + to_remove, + to_insert: Vec::new(), + }); + } + } + let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if new_allowed_hint_kinds == self.allowed_hint_kinds { None } else { - self.allowed_hint_kinds = new_allowed_hint_kinds; + let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut considered_hints = - HashMap::>>::default(); - for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges { - let visible_buffer = visible_buffer.read(cx); - let visible_buffer_id = visible_buffer.remote_id(); - match shown_hints_to_clean.entry(visible_buffer_id) { - hash_map::Entry::Occupied(mut o) => { - let shown_hints_per_excerpt = o.get_mut(); - for (_, shown_hint_id) in shown_hints_per_excerpt - .remove(&visible_excerpt_id) - .unwrap_or_default() - { - considered_hints - .entry(visible_buffer_id) - .or_default() - .entry(visible_excerpt_id) - .or_default() - .insert(shown_hint_id); - match self.inlay_hints.get(&shown_hint_id) { - Some(shown_hint) => { - if !self.allowed_hint_kinds.contains(&shown_hint.kind) { - to_remove.push(shown_hint_id); + let mut shown_hints_to_remove = currently_shown_hints; + + // TODO kb move into a background task + for (buffer_id, cached_buffer_hints) in &self.hints_in_buffers { + let shown_buffer_hints_to_remove = + shown_hints_to_remove.entry(*buffer_id).or_default(); + for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt { + let shown_excerpt_hints_to_remove = + shown_buffer_hints_to_remove.entry(*excerpt_id).or_default(); + let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + loop { + match cached_hints.peek() { + Some((cached_anchor, cached_hint_id)) => { + if cached_hint_id == shown_hint_id { + return !new_allowed_hint_kinds.contains( + &self.inlay_hints.get(&cached_hint_id).unwrap().kind, + ); + } + + match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) { + cmp::Ordering::Less | cmp::Ordering::Equal => { + let maybe_missed_cached_hint = + self.inlay_hints.get(&cached_hint_id).unwrap(); + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !self.allowed_hint_kinds.contains(&cached_hint_kind) + && new_allowed_hint_kinds + .contains(&cached_hint_kind) + { + to_insert.push(( + *cached_hint_id, + *cached_anchor, + maybe_missed_cached_hint.clone(), + )); + } + cached_hints.next(); + } + cmp::Ordering::Greater => break, } } - None => to_remove.push(shown_hint_id), + None => return true, } } - if shown_hints_per_excerpt.is_empty() { - o.remove(); + + match self.inlay_hints.get(&shown_hint_id) { + Some(shown_hint) => !new_allowed_hint_kinds.contains(&shown_hint.kind), + None => true, + } + }); + + for (cached_anchor, cached_hint_id) in cached_hints { + let maybe_missed_cached_hint = + self.inlay_hints.get(&cached_hint_id).unwrap(); + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !self.allowed_hint_kinds.contains(&cached_hint_kind) + && new_allowed_hint_kinds.contains(&cached_hint_kind) + { + to_insert.push(( + *cached_hint_id, + *cached_anchor, + maybe_missed_cached_hint.clone(), + )); } } - hash_map::Entry::Vacant(_) => {} } } - let reenabled_hints = self - .hints_in_buffers - .iter() - .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| { - let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?; - let not_considered_cached_hints = cached_hints_per_excerpt - .hints_per_excerpt - .iter() - .filter_map(|(cached_excerpt_id, cached_excerpt_hints)| { - let considered_excerpt_hints = - considered_hints_in_excerpts.get(&cached_excerpt_id)?; - let not_considered_cached_hints = cached_excerpt_hints - .hints - .iter() - .filter(|(_, cached_hint_id)| { - !considered_excerpt_hints.contains(cached_hint_id) - }) - .copied(); - Some(not_considered_cached_hints) - }) - .flatten(); - Some(not_considered_cached_hints) - }) - .flatten() - .filter_map(|(cached_anchor, cached_hint_id)| { - Some(( - cached_anchor, - cached_hint_id, - self.inlay_hints.get(&cached_hint_id)?, - )) - }) - .filter(|(_, _, cached_hint)| self.allowed_hint_kinds.contains(&cached_hint.kind)) - .map(|(cached_anchor, cached_hint_id, reenabled_hint)| { - (cached_hint_id, cached_anchor, reenabled_hint.clone()) - }); - to_insert.extend(reenabled_hints); - to_remove.extend( - shown_hints_to_clean + shown_hints_to_remove .into_iter() .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) .flat_map(|(_, excerpt_hints)| excerpt_hints) .map(|(_, hint_id)| hint_id), ); - + self.allowed_hint_kinds = new_allowed_hint_kinds; Some(InlaySplice { to_remove, to_insert, @@ -182,46 +171,56 @@ impl InlayHintCache { } } - pub fn clear(&mut self) -> Vec { - let ids_to_remove = self.inlay_hints.drain().map(|(id, _)| id).collect(); - self.hints_in_buffers.clear(); - ids_to_remove - } - pub fn update_hints( &mut self, multi_buffer: ModelHandle, - range_updates: Vec, + queries: Vec, currently_shown_hints: HashMap>>, - conflicts_invalidate_cache: bool, cx: &mut ViewContext, ) -> Task> { - let conflicts_with_cache = conflicts_invalidate_cache - && range_updates.iter().any(|update_query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) + let conflicts_with_cache = queries.iter().any(|update_query| { + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) else { return false }; + if cached_buffer_hints + .buffer_version + .changed_since(&update_query.buffer_version) + { + false + } else if update_query + .buffer_version + .changed_since(&cached_buffer_hints.buffer_version) + { + true + } else { + cached_buffer_hints + .hints_per_excerpt + .contains_key(&update_query.excerpt_id) + } + }); + + // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed + let queries = queries + .into_iter() + .filter_map(|query| { + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id) + else { return Some(query) }; if cached_buffer_hints .buffer_version - .changed_since(&update_query.buffer_version) + .changed_since(&query.buffer_version) { - false - } else if update_query - .buffer_version - .changed_since(&cached_buffer_hints.buffer_version) + return None; + } + if conflicts_with_cache + || !cached_buffer_hints + .hints_per_excerpt + .contains_key(&query.excerpt_id) { - true + Some(query) } else { - cached_buffer_hints - .hints_per_excerpt - .contains_key(&update_query.excerpt_id) + None } - }); - - let queries = filter_queries( - range_updates.into_iter(), - &self.hints_in_buffers, - conflicts_with_cache, - ); + }) + .collect::>(); let task_multi_buffer = multi_buffer.clone(); let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); let mut to_remove = Vec::new(); @@ -255,7 +254,7 @@ impl InlayHintCache { |(excerpt_id, excerpt_hints)| { ( *excerpt_id, - excerpt_hints.hints.iter().map(|(_, id)| *id).collect(), + excerpt_hints.iter().map(|(_, id)| *id).collect(), ) }, ), @@ -276,15 +275,14 @@ impl InlayHintCache { .or_default(); let empty_shown_excerpt_hints = Vec::new(); let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); - for new_hint in new_hints_per_excerpt.hints { + for new_hint in new_hints_per_excerpt { let new_hint_anchor = multi_buffer_snapshot .anchor_in_excerpt(new_excerpt_id, new_hint.position); - let cache_insert_ix = match cached_excerpt_hints.hints.binary_search_by(|probe| { + let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) }) { Ok(ix) => { - let (_, cached_inlay_id) = - cached_excerpt_hints.hints[ix]; + let (_, cached_inlay_id) = cached_excerpt_hints[ix]; let cache_hit = editor .inlay_hint_cache .inlay_hints @@ -331,25 +329,13 @@ impl InlayHintCache { } }; excerpt_cache_hints_to_persist.insert(hint_id); - cached_excerpt_hints - .hints - .insert(insert_ix, (new_hint_anchor, hint_id)); + cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); editor .inlay_hint_cache .inlay_hints .insert(hint_id, new_hint); } } - - if conflicts_with_cache { - cached_excerpt_hints.cached_excerpt_offsets.clear(); - } - for new_range in new_hints_per_excerpt.cached_excerpt_offsets { - insert_and_merge_ranges( - &mut cached_excerpt_hints.cached_excerpt_offsets, - &new_range, - ) - } } } @@ -373,7 +359,7 @@ impl InlayHintCache { buffer_hints.buffer_version = buffer_hints_to_persist.0; buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; - excerpt_hints.hints.retain(|(_, hint_id)| { + excerpt_hints.retain(|(_, hint_id)| { let retain = excerpt_hints_to_persist.contains(hint_id); if !retain { editor @@ -383,7 +369,7 @@ impl InlayHintCache { } retain }); - !excerpt_hints.hints.is_empty() + !excerpt_hints.is_empty() }); !buffer_hints.hints_per_excerpt.is_empty() }); @@ -398,53 +384,6 @@ impl InlayHintCache { } } -fn filter_queries( - queries: impl Iterator, - cached_hints: &HashMap>, - invalidate_cache: bool, -) -> Vec { - // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed - queries - .filter_map(|query| { - let Some(cached_buffer_hints) = cached_hints.get(&query.buffer_id) - else { return Some(vec![query]) }; - if cached_buffer_hints - .buffer_version - .changed_since(&query.buffer_version) - { - return None; - } - let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&query.excerpt_id) - else { return Some(vec![query]) }; - - if invalidate_cache { - Some(vec![query]) - } else { - let non_cached_ranges = missing_subranges( - &excerpt_hints.cached_excerpt_offsets, - &query.excerpt_offset_query_range, - ); - if non_cached_ranges.is_empty() { - None - } else { - Some( - non_cached_ranges - .into_iter() - .map(|non_cached_range| InlayHintQuery { - buffer_id: query.buffer_id, - buffer_version: query.buffer_version.clone(), - excerpt_id: query.excerpt_id, - excerpt_offset_query_range: non_cached_range, - }) - .collect(), - ) - } - } - }) - .flatten() - .collect() -} - fn allowed_hint_types( inlay_hint_settings: editor_settings::InlayHints, ) -> HashSet> { @@ -461,78 +400,6 @@ fn allowed_hint_types( new_allowed_hint_types } -fn missing_subranges(cache: &[Range], input: &Range) -> Vec> { - let mut missing = Vec::new(); - - // Find where the input range would fit in the cache - let index = match cache.binary_search_by_key(&input.start, |probe| probe.start) { - Ok(pos) | Err(pos) => pos, - }; - - // Check for a gap from the start of the input range to the first range in the cache - if index == 0 { - if input.start < cache[index].start { - missing.push(input.start..cache[index].start); - } - } else { - let prev_end = cache[index - 1].end; - if input.start < prev_end { - missing.push(input.start..prev_end); - } - } - - // Iterate through the cache ranges starting from index - for i in index..cache.len() { - let start = if i > 0 { cache[i - 1].end } else { input.start }; - let end = cache[i].start; - - if start < end { - missing.push(start..end); - } - } - - // Check for a gap from the last range in the cache to the end of the input range - if let Some(last_range) = cache.last() { - if last_range.end < input.end { - missing.push(last_range.end..input.end); - } - } else { - // If cache is empty, the entire input range is missing - missing.push(input.start..input.end); - } - - missing -} - -fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range) { - if cache.is_empty() { - cache.push(new_range.clone()); - return; - } - - // Find the index to insert the new range - let index = match cache.binary_search_by_key(&new_range.start, |probe| probe.start) { - Ok(pos) | Err(pos) => pos, - }; - - // Check if the new range overlaps with the previous range in the cache - if index > 0 && cache[index - 1].end >= new_range.start { - // Merge with the previous range - cache[index - 1].end = cmp::max(cache[index - 1].end, new_range.end); - } else { - // Insert the new range, as it doesn't overlap with the previous range - cache.insert(index, new_range.clone()); - } - - // Merge overlaps with subsequent ranges - let mut i = index; - while i + 1 < cache.len() && cache[i].end >= cache[i + 1].start { - cache[i].end = cmp::max(cache[i].end, cache[i + 1].end); - cache.remove(i + 1); - i += 1; - } -} - fn fetch_queries( multi_buffer: ModelHandle, queries: impl Iterator, @@ -546,15 +413,23 @@ fn fetch_queries( else { return anyhow::Ok((query, Some(Vec::new()))) }; let task = editor .update(&mut cx, |editor, cx| { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - project.query_inlay_hints_for_buffer( - buffer_handle, - query.excerpt_offset_query_range.clone(), - cx, - ) + if let Some((_, excerpt_range)) = task_multi_buffer.read(cx) + .excerpts_for_buffer(&buffer_handle, cx) + .into_iter() + .find(|(excerpt_id, _)| excerpt_id == &query.excerpt_id) + { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + project.query_inlay_hints_for_buffer( + buffer_handle, + excerpt_range.context, + cx, + ) + }) }) - }) + } else { + None + } }) .context("inlays fetch task spawn")?; Ok(( @@ -587,13 +462,11 @@ fn fetch_queries( .hints_per_excerpt .entry(query.excerpt_id) .or_default(); - insert_and_merge_ranges(&mut cached_excerpt_hints.cached_excerpt_offsets, &query.excerpt_offset_query_range); - let excerpt_hints = &mut cached_excerpt_hints.hints; for inlay in response_hints { - match excerpt_hints.binary_search_by(|probe| { + match cached_excerpt_hints.binary_search_by(|probe| { inlay.position.cmp(&probe.position, &buffer_snapshot) }) { - Ok(ix) | Err(ix) => excerpt_hints.insert(ix, inlay), + Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, inlay), } } } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 62986b69368c5bce40844ba4543dac17dced6f63..f623ad03f2962885c47311a6dacc603d0978aac3 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -18,7 +18,6 @@ use workspace::WorkspaceId; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, - inlay_hint_cache::InlayRefreshReason, persistence::DB, Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, }; @@ -177,7 +176,7 @@ impl ScrollManager { autoscroll: bool, workspace_id: Option, cx: &mut ViewContext, - ) -> ScrollAnchor { + ) { let (new_anchor, top_row) = if scroll_position.y() <= 0. { ( ScrollAnchor { @@ -206,7 +205,6 @@ impl ScrollManager { }; self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx); - new_anchor } fn set_anchor( @@ -295,12 +293,8 @@ impl Editor { self.scroll_manager.visible_line_count } - pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { - let had_no_visibles = self.scroll_manager.visible_line_count.is_none(); + pub(crate) fn set_visible_line_count(&mut self, lines: f32) { self.scroll_manager.visible_line_count = Some(lines); - if had_no_visibles { - self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); - } } pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { @@ -318,7 +312,7 @@ impl Editor { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - let scroll_anchor = self.scroll_manager.set_scroll_position( + self.scroll_manager.set_scroll_position( scroll_position, &map, local, @@ -326,7 +320,6 @@ impl Editor { workspace_id, cx, ); - self.refresh_inlays(InlayRefreshReason::Scroll(scroll_anchor), cx); } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { From 9698b515247144b9020fdedb815f61bb54d39924 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 20 Jun 2023 10:02:24 +0200 Subject: [PATCH 119/169] Prevent insertion of empty inlays into `InlayMap` --- crates/editor/src/display_map/inlay_map.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 43f86287d8080c519cf377c304d3d96478066d70..acd26a28f7e5cfb1ac6c63902ff77c033f499a9e 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -474,6 +474,12 @@ impl InlayMap { position: properties.position, text: properties.text.into(), }; + + // Avoid inserting empty inlays. + if inlay.text.is_empty() { + continue; + } + self.inlays_by_id.insert(inlay.id, inlay.clone()); match self .inlays @@ -521,7 +527,11 @@ impl InlayMap { if self.inlays.is_empty() || rng.gen() { let position = snapshot.buffer.random_byte_range(0, rng).start; let bias = if rng.gen() { Bias::Left } else { Bias::Right }; - let len = rng.gen_range(1..=5); + let len = if rng.gen_bool(0.01) { + 0 + } else { + rng.gen_range(1..=5) + }; let text = util::RandomCharIter::new(&mut *rng) .filter(|ch| *ch != '\r') .take(len) From a31d3eca45b2f7399a84eeeda92b3cecb70b49ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 01:29:30 +0300 Subject: [PATCH 120/169] Spawn cache updates in separate tasks --- crates/editor/src/editor.rs | 85 +-- crates/editor/src/inlay_hint_cache.rs | 892 ++++++++++++++++++-------- 2 files changed, 651 insertions(+), 326 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8aa5e30742aa66d3da6bc73f2b0cd743423b7124..5be9961e11764c5cea01cafc57a1b17b0fb80886 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24,7 +24,7 @@ pub mod test; use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; use client::{ClickhouseEvent, TelemetrySettings}; use clock::ReplicaId; @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_cache::{InlayHintCache, InlayHintQuery, InlayRefreshReason, InlaySplice}; +use inlay_hint_cache::{InlayHintCache, InlayHintQuery}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1193,6 +1193,12 @@ enum GotoDefinitionKind { Type, } +#[derive(Debug, Copy, Clone)] +enum InlayRefreshReason { + SettingsChange(editor_settings::InlayHints), + VisibleExcerptsChange, +} + impl Editor { pub fn single_line( field_editor_style: Option>, @@ -1360,7 +1366,10 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), + inlay_hint_cache: InlayHintCache::new( + settings::get::(cx).inlay_hints, + cx, + ), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2605,40 +2614,16 @@ impl Editor { let multi_buffer_handle = self.buffer().clone(); let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx); - let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold( - HashMap::>>::default(), - |mut current_hints, inlay| { - if let Some(buffer_id) = inlay.position.buffer_id { - let excerpt_hints = current_hints - .entry(buffer_id) - .or_default() - .entry(inlay.position.excerpt_id) - .or_default(); - match excerpt_hints.binary_search_by(|probe| { - inlay.position.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => { - excerpt_hints.insert(ix, (inlay.position, inlay.id)); - } - } - } - current_hints - }, - ); + let current_inlays = self + .display_map + .read(cx) + .current_inlays() + .cloned() + .collect(); match reason { - InlayRefreshReason::SettingsChange(new_settings) => { - if let Some(InlaySplice { - to_remove, - to_insert, - }) = self.inlay_hint_cache.apply_settings( - &multi_buffer_handle, - new_settings, - currently_shown_inlay_hints, - cx, - ) { - self.splice_inlay_hints(to_remove, to_insert, cx); - } - } + InlayRefreshReason::SettingsChange(new_settings) => self + .inlay_hint_cache + .spawn_settings_update(multi_buffer_handle, new_settings, current_inlays), InlayRefreshReason::VisibleExcerptsChange => { let replacement_queries = self .excerpt_visible_offsets(&multi_buffer_handle, cx) @@ -2652,28 +2637,12 @@ impl Editor { } }) .collect::>(); - cx.spawn(|editor, mut cx| async move { - let InlaySplice { - to_remove, - to_insert, - } = editor - .update(&mut cx, |editor, cx| { - editor.inlay_hint_cache.update_hints( - multi_buffer_handle, - replacement_queries, - currently_shown_inlay_hints, - cx, - ) - })? - .await - .context("inlay cache hint fetch")?; - - editor.update(&mut cx, |editor, cx| { - editor.splice_inlay_hints(to_remove, to_insert, cx) - }) - }) - // TODO kb needs cancellation for many excerpts cases like `project search "test"` - .detach_and_log_err(cx); + self.inlay_hint_cache.spawn_hints_update( + multi_buffer_handle, + replacement_queries, + current_inlays, + cx, + ) } }; } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 7419a8e4847181cf4d6f074e3f9e71958eee8892..548edb1617efd1022021ffd54153b78e0899e87d 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,26 +1,24 @@ use std::cmp; -use crate::{editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer}; +use crate::{ + display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, + MultiBufferSnapshot, +}; use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; -use collections::{HashMap, HashSet}; +use collections::{hash_map, HashMap, HashSet}; use util::post_inc; -#[derive(Debug, Copy, Clone)] -pub enum InlayRefreshReason { - SettingsChange(editor_settings::InlayHints), - VisibleExcerptsChange, -} - -#[derive(Debug, Default)] +#[derive(Debug)] pub struct InlayHintCache { inlay_hints: HashMap, hints_in_buffers: HashMap>, allowed_hint_kinds: HashSet>, + hint_updates_tx: smol::channel::Sender, } #[derive(Clone, Debug)] @@ -29,6 +27,13 @@ struct BufferHints { hints_per_excerpt: HashMap>, } +#[derive(Debug)] +pub struct InlayHintQuery { + pub buffer_id: u64, + pub buffer_version: Global, + pub excerpt_id: ExcerptId, +} + impl BufferHints { fn new(buffer_version: Global) -> Self { Self { @@ -38,146 +43,67 @@ impl BufferHints { } } -#[derive(Debug, Default)] -pub struct InlaySplice { - pub to_remove: Vec, - pub to_insert: Vec<(InlayId, Anchor, InlayHint)>, -} - -#[derive(Debug)] -pub struct InlayHintQuery { - pub buffer_id: u64, - pub buffer_version: Global, - pub excerpt_id: ExcerptId, -} - impl InlayHintCache { - pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { + pub fn new( + inlay_hint_settings: editor_settings::InlayHints, + cx: &mut ViewContext, + ) -> Self { + let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded(); + spawn_hints_update_loop(hint_updates_rx, cx); Self { allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), hints_in_buffers: HashMap::default(), inlay_hints: HashMap::default(), + hint_updates_tx, } } - pub fn apply_settings( + pub fn spawn_settings_update( &mut self, - multi_buffer: &ModelHandle, + multi_buffer: ModelHandle, inlay_hint_settings: editor_settings::InlayHints, - currently_shown_hints: HashMap>>, - cx: &mut ViewContext, - ) -> Option { + current_inlays: Vec, + ) { if !inlay_hint_settings.enabled { self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if self.inlay_hints.is_empty() { - return None; + return; } else { - let to_remove = self.inlay_hints.keys().copied().collect(); - self.inlay_hints.clear(); - self.hints_in_buffers.clear(); - return Some(InlaySplice { - to_remove, - to_insert: Vec::new(), - }); + self.hint_updates_tx + .send_blocking(HintsUpdate { + multi_buffer, + current_inlays, + kind: HintsUpdateKind::Clean, + }) + .ok(); + return; } } let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if new_allowed_hint_kinds == self.allowed_hint_kinds { - None - } else { - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = currently_shown_hints; - - // TODO kb move into a background task - for (buffer_id, cached_buffer_hints) in &self.hints_in_buffers { - let shown_buffer_hints_to_remove = - shown_hints_to_remove.entry(*buffer_id).or_default(); - for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt { - let shown_excerpt_hints_to_remove = - shown_buffer_hints_to_remove.entry(*excerpt_id).or_default(); - let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable(); - shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { - loop { - match cached_hints.peek() { - Some((cached_anchor, cached_hint_id)) => { - if cached_hint_id == shown_hint_id { - return !new_allowed_hint_kinds.contains( - &self.inlay_hints.get(&cached_hint_id).unwrap().kind, - ); - } - - match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) { - cmp::Ordering::Less | cmp::Ordering::Equal => { - let maybe_missed_cached_hint = - self.inlay_hints.get(&cached_hint_id).unwrap(); - let cached_hint_kind = maybe_missed_cached_hint.kind; - if !self.allowed_hint_kinds.contains(&cached_hint_kind) - && new_allowed_hint_kinds - .contains(&cached_hint_kind) - { - to_insert.push(( - *cached_hint_id, - *cached_anchor, - maybe_missed_cached_hint.clone(), - )); - } - cached_hints.next(); - } - cmp::Ordering::Greater => break, - } - } - None => return true, - } - } - - match self.inlay_hints.get(&shown_hint_id) { - Some(shown_hint) => !new_allowed_hint_kinds.contains(&shown_hint.kind), - None => true, - } - }); - - for (cached_anchor, cached_hint_id) in cached_hints { - let maybe_missed_cached_hint = - self.inlay_hints.get(&cached_hint_id).unwrap(); - let cached_hint_kind = maybe_missed_cached_hint.kind; - if !self.allowed_hint_kinds.contains(&cached_hint_kind) - && new_allowed_hint_kinds.contains(&cached_hint_kind) - { - to_insert.push(( - *cached_hint_id, - *cached_anchor, - maybe_missed_cached_hint.clone(), - )); - } - } - } - } + return; + } - to_remove.extend( - shown_hints_to_remove - .into_iter() - .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) - .flat_map(|(_, excerpt_hints)| excerpt_hints) - .map(|(_, hint_id)| hint_id), - ); - self.allowed_hint_kinds = new_allowed_hint_kinds; - Some(InlaySplice { - to_remove, - to_insert, + self.hint_updates_tx + .send_blocking(HintsUpdate { + multi_buffer, + current_inlays, + kind: HintsUpdateKind::AllowedHintKindsChanged { + old: self.allowed_hint_kinds.clone(), + new: new_allowed_hint_kinds, + }, }) - } + .ok(); } - pub fn update_hints( + pub fn spawn_hints_update( &mut self, multi_buffer: ModelHandle, queries: Vec, - currently_shown_hints: HashMap>>, + current_inlays: Vec, cx: &mut ViewContext, - ) -> Task> { + ) { let conflicts_with_cache = queries.iter().any(|update_query| { let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) else { return false }; @@ -198,8 +124,7 @@ impl InlayHintCache { } }); - // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed - let queries = queries + let queries_per_buffer = queries .into_iter() .filter_map(|query| { let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id) @@ -220,167 +145,410 @@ impl InlayHintCache { None } }) - .collect::>(); - let task_multi_buffer = multi_buffer.clone(); - let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - let mut cache_hints_to_persist: HashMap< - u64, - (Global, HashMap>), - > = HashMap::default(); - cx.spawn(|editor, mut cx| async move { - let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; - editor.update(&mut cx, |editor, cx| { - let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); - for (new_buffer_id, new_hints_per_buffer) in new_hints { - let cached_buffer_hints = editor - .inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new(new_hints_per_buffer.buffer_version.clone()) - }); + .fold( + HashMap::)>::default(), + |mut queries_per_buffer, new_query| { + let (current_verison, excerpts_to_query) = + queries_per_buffer.entry(new_query.buffer_id).or_default(); - let buffer_cache_hints_to_persist = - cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); - if cached_buffer_hints - .buffer_version - .changed_since(&new_hints_per_buffer.buffer_version) - { - buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; - buffer_cache_hints_to_persist.1.extend( - cached_buffer_hints.hints_per_excerpt.iter().map( - |(excerpt_id, excerpt_hints)| { - ( - *excerpt_id, - excerpt_hints.iter().map(|(_, id)| *id).collect(), - ) - }, - ), - ); - continue; + if new_query.buffer_version.changed_since(current_verison) { + *current_verison = new_query.buffer_version; + *excerpts_to_query = vec![new_query.excerpt_id]; + } else if !current_verison.changed_since(&new_query.buffer_version) { + excerpts_to_query.push(new_query.excerpt_id); } - let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); - for (new_excerpt_id, new_hints_per_excerpt) in - new_hints_per_buffer.hints_per_excerpt - { - let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 - .entry(new_excerpt_id) - .or_default(); - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(new_excerpt_id) - .or_default(); - let empty_shown_excerpt_hints = Vec::new(); - let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); - for new_hint in new_hints_per_excerpt { - let new_hint_anchor = multi_buffer_snapshot - .anchor_in_excerpt(new_excerpt_id, new_hint.position); - let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { - new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) => { - let (_, cached_inlay_id) = cached_excerpt_hints[ix]; - let cache_hit = editor - .inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint) - .is_some(); - if cache_hit { - excerpt_cache_hints_to_persist - .insert(cached_inlay_id); - None - } else { - Some(ix) - } - } - Err(ix) => Some(ix), - }; - - let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { - probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) - }) { - Ok(ix) => {{ - let (_, shown_inlay_id) = shown_excerpt_hints[ix]; - let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint).is_some(); - if shown_hint_found { - Some(shown_inlay_id) - } else { - None - } - }}, - Err(_) => None, - }; - - if let Some(insert_ix) = cache_insert_ix { - let hint_id = match shown_inlay_id { - Some(shown_inlay_id) => shown_inlay_id, - None => { - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); - if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) - { - to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); - } - new_hint_id - } - }; - excerpt_cache_hints_to_persist.insert(hint_id); - cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); - editor - .inlay_hint_cache - .inlay_hints - .insert(hint_id, new_hint); - } + queries_per_buffer + }, + ); + + for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer { + self.hint_updates_tx + .send_blocking(HintsUpdate { + multi_buffer, + current_inlays, + kind: HintsUpdateKind::BufferUpdate { + invalidate_cache: conflicts_with_cache, + buffer_id: queried_buffer, + buffer_version, + excerpts, + }, + }) + .ok(); + } + } +} + +#[derive(Debug, Default)] +struct InlaySplice { + to_remove: Vec, + to_insert: Vec<(InlayId, Anchor, InlayHint)>, +} + +struct HintsUpdate { + multi_buffer: ModelHandle, + current_inlays: Vec, + kind: HintsUpdateKind, +} + +enum HintsUpdateKind { + Clean, + AllowedHintKindsChanged { + old: HashSet>, + new: HashSet>, + }, + BufferUpdate { + buffer_id: u64, + buffer_version: Global, + excerpts: Vec, + invalidate_cache: bool, + }, +} + +struct UpdateTaskHandle { + multi_buffer: ModelHandle, + cancellation_tx: smol::channel::Sender<()>, + task_finish_rx: smol::channel::Receiver, +} + +struct UpdateTaskResult { + multi_buffer: ModelHandle, + splice: InlaySplice, + new_allowed_hint_kinds: Option>>, + remove_from_cache: HashSet, + add_to_cache: HashMap>, +} + +impl HintsUpdate { + fn merge(&mut self, mut other: Self) -> Result<(), Self> { + match (&mut self.kind, &mut other.kind) { + (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()), + ( + HintsUpdateKind::AllowedHintKindsChanged { .. }, + HintsUpdateKind::AllowedHintKindsChanged { .. }, + ) => { + *self = other; + return Ok(()); + } + ( + HintsUpdateKind::BufferUpdate { + buffer_id: old_buffer_id, + buffer_version: old_buffer_version, + excerpts: old_excerpts, + invalidate_cache: old_invalidate_cache, + }, + HintsUpdateKind::BufferUpdate { + buffer_id: new_buffer_id, + buffer_version: new_buffer_version, + excerpts: new_excerpts, + invalidate_cache: new_invalidate_cache, + }, + ) => { + if old_buffer_id == new_buffer_id { + if new_buffer_version.changed_since(old_buffer_version) { + *self = other; + return Ok(()); + } else if old_buffer_version.changed_since(new_buffer_version) { + return Ok(()); + } else if *new_invalidate_cache { + *self = other; + return Ok(()); + } else { + let old_inlays = self + .current_inlays + .iter() + .map(|inlay| inlay.id) + .collect::>(); + let new_inlays = other + .current_inlays + .iter() + .map(|inlay| inlay.id) + .collect::>(); + if old_inlays == new_inlays { + old_excerpts.extend(new_excerpts.drain(..)); + old_excerpts.dedup(); + return Ok(()); } } } + } + _ => {} + } + + Err(other) + } + + fn spawn(self, cx: &mut ViewContext<'_, '_, Editor>) -> UpdateTaskHandle { + let (task_finish_tx, task_finish_rx) = smol::channel::unbounded(); + let (cancellation_tx, cancellation_rx) = smol::channel::bounded(1); + + match self.kind { + HintsUpdateKind::Clean => cx + .spawn(|editor, mut cx| async move { + if let Some(splice) = editor.update(&mut cx, |editor, cx| { + clean_cache(editor, self.current_inlays) + })? { + task_finish_tx + .send(UpdateTaskResult { + multi_buffer: self.multi_buffer.clone(), + splice, + new_allowed_hint_kinds: None, + remove_from_cache: HashSet::default(), + add_to_cache: HashMap::default(), + }) + .await + .ok(); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx), + HintsUpdateKind::AllowedHintKindsChanged { old, new } => cx + .spawn(|editor, mut cx| async move { + if let Some(splice) = editor.update(&mut cx, |editor, cx| { + update_allowed_hint_kinds( + &self.multi_buffer.read(cx).snapshot(cx), + self.current_inlays, + old, + new, + editor, + ) + })? { + task_finish_tx + .send(UpdateTaskResult { + multi_buffer: self.multi_buffer.clone(), + splice, + new_allowed_hint_kinds: None, + remove_from_cache: HashSet::default(), + add_to_cache: HashMap::default(), + }) + .await + .ok(); + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx), + HintsUpdateKind::BufferUpdate { + buffer_id, + buffer_version, + excerpts, + invalidate_cache, + } => todo!("TODO kb"), + } - if conflicts_with_cache { - for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { - match cache_hints_to_persist.get(&shown_buffer_id) { - Some(cached_buffer_hints) => { - for (persisted_id, cached_hints) in &cached_buffer_hints.1 { - shown_hints_to_clean.entry(*persisted_id).or_default() - .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); + UpdateTaskHandle { + multi_buffer: self.multi_buffer.clone(), + cancellation_tx, + task_finish_rx, + } + } +} + +fn spawn_hints_update_loop( + hint_updates_rx: smol::channel::Receiver, + cx: &mut ViewContext<'_, '_, Editor>, +) { + cx.spawn(|editor, mut cx| async move { + let mut update = None::; + let mut next_update = None::; + loop { + if update.is_none() { + match hint_updates_rx.recv().await { + Ok(first_task) => update = Some(first_task), + Err(smol::channel::RecvError) => return, + } + } + + let mut updates_limit = 10; + 'update_merge: loop { + match hint_updates_rx.try_recv() { + Ok(new_update) => { + match update.as_mut() { + Some(update) => match update.merge(new_update) { + Ok(()) => {} + Err(new_update) => { + next_update = Some(new_update); + break 'update_merge; } }, - None => {}, + None => update = Some(new_update), + }; + + if updates_limit == 0 { + break 'update_merge; } - to_remove.extend(shown_hints_to_clean.into_iter() - .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); + updates_limit -= 1; } + Err(smol::channel::TryRecvError::Empty) => break 'update_merge, + Err(smol::channel::TryRecvError::Closed) => return, + } + } - editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { - let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; - buffer_hints.buffer_version = buffer_hints_to_persist.0; - buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { - let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; - excerpt_hints.retain(|(_, hint_id)| { - let retain = excerpt_hints_to_persist.contains(hint_id); - if !retain { - editor - .inlay_hint_cache - .inlay_hints - .remove(hint_id); - } - retain + if let Some(update) = update.take() { + let Ok(task_handle) = editor.update(&mut cx, |_, cx| update.spawn(cx)) else { return; }; + while let Ok(update_task_result) = task_handle.task_finish_rx.recv().await { + let Ok(()) = editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = update_task_result.multi_buffer.read(cx).snapshot(cx); + let inlay_hint_cache = &mut editor.inlay_hint_cache; + + if let Some(new_allowed_hint_kinds) = update_task_result.new_allowed_hint_kinds { + inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds; + } + + inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { + buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { + excerpt_hints.retain(|(_, hint_id)| !update_task_result.remove_from_cache.contains(hint_id)); + !excerpt_hints.is_empty() }); - !excerpt_hints.is_empty() + !buffer_hints.hints_per_excerpt.is_empty() }); - !buffer_hints.hints_per_excerpt.is_empty() - }); + inlay_hint_cache.inlay_hints.retain(|hint_id, _| !update_task_result.remove_from_cache.contains(hint_id)); + + for (new_buffer_id, new_buffer_inlays) in update_task_result.add_to_cache { + let cached_buffer_hints = inlay_hint_cache.hints_in_buffers.entry(new_buffer_id).or_insert_with(|| BufferHints::new(new_buffer_inlays.buffer_version)); + if cached_buffer_hints.buffer_version.changed_since(&new_buffer_inlays.buffer_version) { + continue; + } + for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays.hints_per_excerpt { + let cached_excerpt_hints = cached_buffer_hints.hints_per_excerpt.entry(excerpt_id).or_default(); + for (new_hint_position, new_hint, new_inlay_id) in new_excerpt_inlays { + if let hash_map::Entry::Vacant(v) = inlay_hint_cache.inlay_hints.entry(new_inlay_id) { + v.insert(new_hint); + match cached_excerpt_hints.binary_search_by(|probe| { + new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, (new_hint_position, new_inlay_id)), + } + } + } + } + } + + let InlaySplice { + to_remove, + to_insert, + } = update_task_result.splice; + editor.splice_inlay_hints(to_remove, to_insert, cx) + }) else { return; }; } + } + update = next_update.take(); + } + }) + .detach() +} + +fn update_allowed_hint_kinds( + multi_buffer_snapshot: &MultiBufferSnapshot, + current_inlays: Vec, + old_kinds: HashSet>, + new_kinds: HashSet>, + editor: &mut Editor, +) -> Option { + if old_kinds == new_kinds { + return None; + } - InlaySplice { - to_remove, - to_insert, + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let mut shown_hints_to_remove = group_inlays(&multi_buffer_snapshot, current_inlays); + let hints_cache = &editor.inlay_hint_cache; + + for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers { + let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default(); + for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt { + let shown_excerpt_hints_to_remove = + shown_buffer_hints_to_remove.entry(*excerpt_id).or_default(); + let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + loop { + match cached_hints.peek() { + Some((cached_anchor, cached_hint_id)) => { + if cached_hint_id == shown_hint_id { + return !new_kinds.contains( + &hints_cache.inlay_hints.get(&cached_hint_id).unwrap().kind, + ); + } + + match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) { + cmp::Ordering::Less | cmp::Ordering::Equal => { + let maybe_missed_cached_hint = + hints_cache.inlay_hints.get(&cached_hint_id).unwrap(); + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) + && new_kinds.contains(&cached_hint_kind) + { + to_insert.push(( + *cached_hint_id, + *cached_anchor, + maybe_missed_cached_hint.clone(), + )); + } + cached_hints.next(); + } + cmp::Ordering::Greater => break, + } + } + None => return true, + } } - }) - }) + + match hints_cache.inlay_hints.get(&shown_hint_id) { + Some(shown_hint) => !new_kinds.contains(&shown_hint.kind), + None => true, + } + }); + + for (cached_anchor, cached_hint_id) in cached_hints { + let maybe_missed_cached_hint = + hints_cache.inlay_hints.get(&cached_hint_id).unwrap(); + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + to_insert.push(( + *cached_hint_id, + *cached_anchor, + maybe_missed_cached_hint.clone(), + )); + } + } + } + } + + to_remove.extend( + shown_hints_to_remove + .into_iter() + .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) + .flat_map(|(_, excerpt_hints)| excerpt_hints) + .map(|(_, hint_id)| hint_id), + ); + Some(InlaySplice { + to_remove, + to_insert, + }) +} + +fn clean_cache(editor: &mut Editor, current_inlays: Vec) -> Option { + let hints_cache = &mut editor.inlay_hint_cache; + if hints_cache.inlay_hints.is_empty() { + None + } else { + let splice = InlaySplice { + to_remove: current_inlays + .iter() + .filter(|inlay| { + editor + .copilot_state + .suggestion + .as_ref() + .map(|inlay| inlay.id) + != Some(inlay.id) + }) + .map(|inlay| inlay.id) + .collect(), + to_insert: Vec::new(), + }; + hints_cache.inlay_hints.clear(); + hints_cache.hints_in_buffers.clear(); + Some(splice) } } @@ -400,6 +568,8 @@ fn allowed_hint_types( new_allowed_hint_types } +// TODO kb wrong, query and update the editor separately +// TODO kb need to react on react on scrolling too, for multibuffer excerpts fn fetch_queries( multi_buffer: ModelHandle, queries: impl Iterator, @@ -477,3 +647,189 @@ fn fetch_queries( Ok(inlay_updates) }) } + +fn group_inlays( + multi_buffer_snapshot: &MultiBufferSnapshot, + inlays: Vec, +) -> HashMap>> { + inlays.into_iter().fold( + HashMap::>>::default(), + |mut current_hints, inlay| { + if let Some(buffer_id) = inlay.position.buffer_id { + current_hints + .entry(buffer_id) + .or_default() + .entry(inlay.position.excerpt_id) + .or_default() + .push((inlay.position, inlay.id)); + } + current_hints + }, + ) +} + +async fn update_hints( + multi_buffer: ModelHandle, + queries: Vec, + current_inlays: Vec, + invalidate_cache: bool, + cx: &mut ViewContext<'_, '_, Editor>, +) -> Option { + let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); + let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let mut cache_hints_to_persist: HashMap>)> = + HashMap::default(); + + editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); + for (new_buffer_id, new_hints_per_buffer) in new_hints { + let cached_buffer_hints = editor + .inlay_hint_cache + .hints_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferHints::new(new_hints_per_buffer.buffer_version.clone()) + }); + + let buffer_cache_hints_to_persist = + cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); + if cached_buffer_hints + .buffer_version + .changed_since(&new_hints_per_buffer.buffer_version) + { + buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; + buffer_cache_hints_to_persist.1.extend( + cached_buffer_hints.hints_per_excerpt.iter().map( + |(excerpt_id, excerpt_hints)| { + ( + *excerpt_id, + excerpt_hints.iter().map(|(_, id)| *id).collect(), + ) + }, + ), + ); + continue; + } + + let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); + for (new_excerpt_id, new_hints_per_excerpt) in + new_hints_per_buffer.hints_per_excerpt + { + let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 + .entry(new_excerpt_id) + .or_default(); + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt + .entry(new_excerpt_id) + .or_default(); + let empty_shown_excerpt_hints = Vec::new(); + let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); + for new_hint in new_hints_per_excerpt { + let new_hint_anchor = multi_buffer_snapshot + .anchor_in_excerpt(new_excerpt_id, new_hint.position); + let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { + new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) => { + let (_, cached_inlay_id) = cached_excerpt_hints[ix]; + let cache_hit = editor + .inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint) + .is_some(); + if cache_hit { + excerpt_cache_hints_to_persist + .insert(cached_inlay_id); + None + } else { + Some(ix) + } + } + Err(ix) => Some(ix), + }; + + let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { + probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) + }) { + Ok(ix) => {{ + let (_, shown_inlay_id) = shown_excerpt_hints[ix]; + let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint).is_some(); + if shown_hint_found { + Some(shown_inlay_id) + } else { + None + } + }}, + Err(_) => None, + }; + + if let Some(insert_ix) = cache_insert_ix { + let hint_id = match shown_inlay_id { + Some(shown_inlay_id) => shown_inlay_id, + None => { + let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) + { + to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); + } + new_hint_id + } + }; + excerpt_cache_hints_to_persist.insert(hint_id); + cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); + editor + .inlay_hint_cache + .inlay_hints + .insert(hint_id, new_hint); + } + } + } + } + + if conflicts_with_cache { + for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { + match cache_hints_to_persist.get(&shown_buffer_id) { + Some(cached_buffer_hints) => { + for (persisted_id, cached_hints) in &cached_buffer_hints.1 { + shown_hints_to_clean.entry(*persisted_id).or_default() + .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); + } + }, + None => {}, + } + to_remove.extend(shown_hints_to_clean.into_iter() + .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); + } + + editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { + let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; + buffer_hints.buffer_version = buffer_hints_to_persist.0; + buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { + let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; + excerpt_hints.retain(|(_, hint_id)| { + let retain = excerpt_hints_to_persist.contains(hint_id); + if !retain { + editor + .inlay_hint_cache + .inlay_hints + .remove(hint_id); + } + retain + }); + !excerpt_hints.is_empty() + }); + !buffer_hints.hints_per_excerpt.is_empty() + }); + } + + Some(InlaySplice { + to_remove, + to_insert, + }) + }) +} From 2f1a27631eca2cd5b48dc82307aa600cdcc28a8a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 01:42:59 +0300 Subject: [PATCH 121/169] React on multibuffer scrolls again --- crates/editor/src/editor.rs | 32 +++++++++++++++++++++- crates/editor/src/inlay_hint_cache.rs | 39 ++++++++++++++------------- crates/editor/src/scroll.rs | 11 +++++--- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5be9961e11764c5cea01cafc57a1b17b0fb80886..cc4978a5dadc1b19a61f419938d64a07ef953580 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1196,6 +1196,7 @@ enum GotoDefinitionKind { #[derive(Debug, Copy, Clone)] enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), + Scroll(ScrollAnchor), VisibleExcerptsChange, } @@ -2624,6 +2625,34 @@ impl Editor { InlayRefreshReason::SettingsChange(new_settings) => self .inlay_hint_cache .spawn_settings_update(multi_buffer_handle, new_settings, current_inlays), + InlayRefreshReason::Scroll(scrolled_to) => { + if let Some(new_query) = self + .excerpt_visible_offsets(&multi_buffer_handle, cx) + .into_iter() + .find_map(|(buffer, _, excerpt_id)| { + let buffer_id = scrolled_to.anchor.buffer_id?; + if buffer_id == buffer.read(cx).remote_id() + && scrolled_to.anchor.excerpt_id == excerpt_id + { + Some(InlayHintQuery { + buffer_id, + buffer_version: buffer.read(cx).version(), + excerpt_id, + }) + } else { + None + } + }) + { + self.inlay_hint_cache.spawn_hints_update( + multi_buffer_handle, + vec![new_query], + current_inlays, + false, + cx, + ) + } + } InlayRefreshReason::VisibleExcerptsChange => { let replacement_queries = self .excerpt_visible_offsets(&multi_buffer_handle, cx) @@ -2632,7 +2661,7 @@ impl Editor { let buffer = buffer.read(cx); InlayHintQuery { buffer_id: buffer.remote_id(), - buffer_version: buffer.version.clone(), + buffer_version: buffer.version(), excerpt_id, } }) @@ -2641,6 +2670,7 @@ impl Editor { multi_buffer_handle, replacement_queries, current_inlays, + true, cx, ) } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 548edb1617efd1022021ffd54153b78e0899e87d..1058266771041b594e681c71d6e8b5bdb76bc26b 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -102,27 +102,29 @@ impl InlayHintCache { multi_buffer: ModelHandle, queries: Vec, current_inlays: Vec, + conflicts_invalidate_cache: bool, cx: &mut ViewContext, ) { - let conflicts_with_cache = queries.iter().any(|update_query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) + let conflicts_with_cache = conflicts_invalidate_cache + && queries.iter().any(|update_query| { + let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) else { return false }; - if cached_buffer_hints - .buffer_version - .changed_since(&update_query.buffer_version) - { - false - } else if update_query - .buffer_version - .changed_since(&cached_buffer_hints.buffer_version) - { - true - } else { - cached_buffer_hints - .hints_per_excerpt - .contains_key(&update_query.excerpt_id) - } - }); + if cached_buffer_hints + .buffer_version + .changed_since(&update_query.buffer_version) + { + false + } else if update_query + .buffer_version + .changed_since(&cached_buffer_hints.buffer_version) + { + true + } else { + cached_buffer_hints + .hints_per_excerpt + .contains_key(&update_query.excerpt_id) + } + }); let queries_per_buffer = queries .into_iter() @@ -569,7 +571,6 @@ fn allowed_hint_types( } // TODO kb wrong, query and update the editor separately -// TODO kb need to react on react on scrolling too, for multibuffer excerpts fn fetch_queries( multi_buffer: ModelHandle, queries: impl Iterator, diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index f623ad03f2962885c47311a6dacc603d0978aac3..45ba0bc664e0728118acef680a123e794960c261 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -13,7 +13,7 @@ use gpui::{ }; use language::{Bias, Point}; use util::ResultExt; -use workspace::WorkspaceId; +use workspace::{item::Item, WorkspaceId}; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, @@ -176,7 +176,7 @@ impl ScrollManager { autoscroll: bool, workspace_id: Option, cx: &mut ViewContext, - ) { + ) -> ScrollAnchor { let (new_anchor, top_row) = if scroll_position.y() <= 0. { ( ScrollAnchor { @@ -205,6 +205,7 @@ impl ScrollManager { }; self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx); + new_anchor } fn set_anchor( @@ -312,7 +313,7 @@ impl Editor { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - self.scroll_manager.set_scroll_position( + let scroll_anchor = self.scroll_manager.set_scroll_position( scroll_position, &map, local, @@ -320,6 +321,10 @@ impl Editor { workspace_id, cx, ); + + if !self.is_singleton(cx) { + self.refresh_inlays(crate::InlayRefreshReason::Scroll(scroll_anchor), cx); + } } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { From 4c78019317b8432b2cb9b0000a19ce828cb5f898 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 11:19:58 +0300 Subject: [PATCH 122/169] Start to model the background threads for InlayHintCache --- crates/editor/src/editor.rs | 27 +- crates/editor/src/inlay_hint_cache.rs | 744 ++++++++++++++------------ 2 files changed, 399 insertions(+), 372 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cc4978a5dadc1b19a61f419938d64a07ef953580..5e8237b8323c9707f8c27f661ea79c1e56474b99 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2613,23 +2613,21 @@ impl Editor { return; } - let multi_buffer_handle = self.buffer().clone(); - let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx); + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); let current_inlays = self .display_map .read(cx) .current_inlays() .cloned() + .filter(|inlay| Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id)) .collect(); match reason { InlayRefreshReason::SettingsChange(new_settings) => self .inlay_hint_cache - .spawn_settings_update(multi_buffer_handle, new_settings, current_inlays), + .spawn_settings_update(multi_buffer_snapshot, new_settings, current_inlays), InlayRefreshReason::Scroll(scrolled_to) => { - if let Some(new_query) = self - .excerpt_visible_offsets(&multi_buffer_handle, cx) - .into_iter() - .find_map(|(buffer, _, excerpt_id)| { + if let Some(new_query) = self.excerpt_visible_offsets(cx).into_iter().find_map( + |(buffer, _, excerpt_id)| { let buffer_id = scrolled_to.anchor.buffer_id?; if buffer_id == buffer.read(cx).remote_id() && scrolled_to.anchor.excerpt_id == excerpt_id @@ -2642,20 +2640,19 @@ impl Editor { } else { None } - }) - { + }, + ) { self.inlay_hint_cache.spawn_hints_update( - multi_buffer_handle, + multi_buffer_snapshot, vec![new_query], current_inlays, false, - cx, ) } } InlayRefreshReason::VisibleExcerptsChange => { let replacement_queries = self - .excerpt_visible_offsets(&multi_buffer_handle, cx) + .excerpt_visible_offsets(cx) .into_iter() .map(|(buffer, _, excerpt_id)| { let buffer = buffer.read(cx); @@ -2667,11 +2664,10 @@ impl Editor { }) .collect::>(); self.inlay_hint_cache.spawn_hints_update( - multi_buffer_handle, + multi_buffer_snapshot, replacement_queries, current_inlays, true, - cx, ) } }; @@ -2679,10 +2675,9 @@ impl Editor { fn excerpt_visible_offsets( &self, - multi_buffer: &ModelHandle, cx: &mut ViewContext<'_, '_, Editor>, ) -> Vec<(ModelHandle, Range, ExcerptId)> { - let multi_buffer = multi_buffer.read(cx); + let multi_buffer = self.buffer().read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); let multi_buffer_visible_start = self .scroll_manager diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 1058266771041b594e681c71d6e8b5bdb76bc26b..0d03787d24b5b4f68ea35be03cdc99773b6d1c89 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -6,12 +6,12 @@ use crate::{ }; use anyhow::Context; use clock::Global; +use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; use gpui::{ModelHandle, Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; -use util::post_inc; #[derive(Debug)] pub struct InlayHintCache { @@ -21,6 +21,13 @@ pub struct InlayHintCache { hint_updates_tx: smol::channel::Sender, } +#[derive(Debug)] +struct CacheSnapshot { + inlay_hints: HashMap, + hints_in_buffers: HashMap>, + allowed_hint_kinds: HashSet>, +} + #[derive(Clone, Debug)] struct BufferHints { buffer_version: Global, @@ -49,7 +56,82 @@ impl InlayHintCache { cx: &mut ViewContext, ) -> Self { let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded(); - spawn_hints_update_loop(hint_updates_rx, cx); + let (update_results_tx, update_results_rx) = smol::channel::unbounded(); + spawn_hints_update_loop(hint_updates_rx, update_results_tx, cx); + cx.spawn(|editor, mut cx| async move { + while let Ok(update_result) = update_results_rx.recv().await { + let editor_absent = editor + .update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx); + let inlay_hint_cache = &mut editor.inlay_hint_cache; + if let Some(new_allowed_hint_kinds) = update_result.new_allowed_hint_kinds { + inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds; + } + + inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { + buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { + excerpt_hints.retain(|(_, hint_id)| { + !update_result.remove_from_cache.contains(hint_id) + }); + !excerpt_hints.is_empty() + }); + !buffer_hints.hints_per_excerpt.is_empty() + }); + inlay_hint_cache.inlay_hints.retain(|hint_id, _| { + !update_result.remove_from_cache.contains(hint_id) + }); + + for (new_buffer_id, new_buffer_inlays) in update_result.add_to_cache { + let cached_buffer_hints = inlay_hint_cache + .hints_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferHints::new(new_buffer_inlays.buffer_version.clone()) + }); + if cached_buffer_hints + .buffer_version + .changed_since(&new_buffer_inlays.buffer_version) + { + continue; + } + for (excerpt_id, new_excerpt_inlays) in + new_buffer_inlays.hints_per_excerpt + { + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt + .entry(excerpt_id) + .or_default(); + for (new_hint_position, new_hint, new_inlay_id) in + new_excerpt_inlays + { + if let hash_map::Entry::Vacant(v) = + inlay_hint_cache.inlay_hints.entry(new_inlay_id) + { + v.insert(new_hint); + match cached_excerpt_hints.binary_search_by(|probe| { + new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => cached_excerpt_hints + .insert(ix, (new_hint_position, new_inlay_id)), + } + } + } + } + } + + let InlaySplice { + to_remove, + to_insert, + } = update_result.splice; + editor.splice_inlay_hints(to_remove, to_insert, cx) + }) + .is_err(); + if editor_absent { + return; + } + } + }) + .detach(); Self { allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), hints_in_buffers: HashMap::default(), @@ -60,7 +142,7 @@ impl InlayHintCache { pub fn spawn_settings_update( &mut self, - multi_buffer: ModelHandle, + multi_buffer_snapshot: MultiBufferSnapshot, inlay_hint_settings: editor_settings::InlayHints, current_inlays: Vec, ) { @@ -71,8 +153,9 @@ impl InlayHintCache { } else { self.hint_updates_tx .send_blocking(HintsUpdate { - multi_buffer, - current_inlays, + multi_buffer_snapshot, + cache: self.snapshot(), + visible_inlays: current_inlays, kind: HintsUpdateKind::Clean, }) .ok(); @@ -87,10 +170,10 @@ impl InlayHintCache { self.hint_updates_tx .send_blocking(HintsUpdate { - multi_buffer, - current_inlays, + multi_buffer_snapshot, + cache: self.snapshot(), + visible_inlays: current_inlays, kind: HintsUpdateKind::AllowedHintKindsChanged { - old: self.allowed_hint_kinds.clone(), new: new_allowed_hint_kinds, }, }) @@ -99,11 +182,10 @@ impl InlayHintCache { pub fn spawn_hints_update( &mut self, - multi_buffer: ModelHandle, + multi_buffer_snapshot: MultiBufferSnapshot, queries: Vec, current_inlays: Vec, conflicts_invalidate_cache: bool, - cx: &mut ViewContext, ) { let conflicts_with_cache = conflicts_invalidate_cache && queries.iter().any(|update_query| { @@ -167,8 +249,9 @@ impl InlayHintCache { for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer { self.hint_updates_tx .send_blocking(HintsUpdate { - multi_buffer, - current_inlays, + multi_buffer_snapshot: multi_buffer_snapshot.clone(), + visible_inlays: current_inlays.clone(), + cache: self.snapshot(), kind: HintsUpdateKind::BufferUpdate { invalidate_cache: conflicts_with_cache, buffer_id: queried_buffer, @@ -179,6 +262,16 @@ impl InlayHintCache { .ok(); } } + + // TODO kb could be big and cloned per symbol input. + // Instead, use `Box`/`Arc`/`Rc`? + fn snapshot(&self) -> CacheSnapshot { + CacheSnapshot { + inlay_hints: self.inlay_hints.clone(), + hints_in_buffers: self.hints_in_buffers.clone(), + allowed_hint_kinds: self.allowed_hint_kinds.clone(), + } + } } #[derive(Debug, Default)] @@ -188,15 +281,15 @@ struct InlaySplice { } struct HintsUpdate { - multi_buffer: ModelHandle, - current_inlays: Vec, + multi_buffer_snapshot: MultiBufferSnapshot, + visible_inlays: Vec, + cache: CacheSnapshot, kind: HintsUpdateKind, } enum HintsUpdateKind { Clean, AllowedHintKindsChanged { - old: HashSet>, new: HashSet>, }, BufferUpdate { @@ -207,14 +300,7 @@ enum HintsUpdateKind { }, } -struct UpdateTaskHandle { - multi_buffer: ModelHandle, - cancellation_tx: smol::channel::Sender<()>, - task_finish_rx: smol::channel::Receiver, -} - -struct UpdateTaskResult { - multi_buffer: ModelHandle, +struct UpdateResult { splice: InlaySplice, new_allowed_hint_kinds: Option>>, remove_from_cache: HashSet, @@ -237,7 +323,7 @@ impl HintsUpdate { buffer_id: old_buffer_id, buffer_version: old_buffer_version, excerpts: old_excerpts, - invalidate_cache: old_invalidate_cache, + .. }, HintsUpdateKind::BufferUpdate { buffer_id: new_buffer_id, @@ -257,12 +343,12 @@ impl HintsUpdate { return Ok(()); } else { let old_inlays = self - .current_inlays + .visible_inlays .iter() .map(|inlay| inlay.id) .collect::>(); let new_inlays = other - .current_inlays + .visible_inlays .iter() .map(|inlay| inlay.id) .collect::>(); @@ -280,180 +366,155 @@ impl HintsUpdate { Err(other) } - fn spawn(self, cx: &mut ViewContext<'_, '_, Editor>) -> UpdateTaskHandle { - let (task_finish_tx, task_finish_rx) = smol::channel::unbounded(); - let (cancellation_tx, cancellation_rx) = smol::channel::bounded(1); - + async fn run(self, result_sender: smol::channel::Sender) { match self.kind { - HintsUpdateKind::Clean => cx - .spawn(|editor, mut cx| async move { - if let Some(splice) = editor.update(&mut cx, |editor, cx| { - clean_cache(editor, self.current_inlays) - })? { - task_finish_tx - .send(UpdateTaskResult { - multi_buffer: self.multi_buffer.clone(), - splice, - new_allowed_hint_kinds: None, - remove_from_cache: HashSet::default(), - add_to_cache: HashMap::default(), - }) - .await - .ok(); - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx), - HintsUpdateKind::AllowedHintKindsChanged { old, new } => cx - .spawn(|editor, mut cx| async move { - if let Some(splice) = editor.update(&mut cx, |editor, cx| { - update_allowed_hint_kinds( - &self.multi_buffer.read(cx).snapshot(cx), - self.current_inlays, - old, - new, - editor, - ) - })? { - task_finish_tx - .send(UpdateTaskResult { - multi_buffer: self.multi_buffer.clone(), - splice, - new_allowed_hint_kinds: None, - remove_from_cache: HashSet::default(), - add_to_cache: HashMap::default(), - }) - .await - .ok(); - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx), + HintsUpdateKind::Clean => { + if !self.cache.inlay_hints.is_empty() || !self.visible_inlays.is_empty() { + result_sender + .send(UpdateResult { + splice: InlaySplice { + to_remove: self + .visible_inlays + .iter() + .map(|inlay| inlay.id) + .collect(), + to_insert: Vec::new(), + }, + new_allowed_hint_kinds: None, + remove_from_cache: self.cache.inlay_hints.keys().copied().collect(), + add_to_cache: HashMap::default(), + }) + .await + .ok(); + } + } + HintsUpdateKind::AllowedHintKindsChanged { new } => { + if let Some(splice) = new_allowed_hint_kinds_splice( + &self.multi_buffer_snapshot, + self.visible_inlays, + &self.cache, + &new, + ) { + result_sender + .send(UpdateResult { + splice, + new_allowed_hint_kinds: Some(new), + remove_from_cache: HashSet::default(), + add_to_cache: HashMap::default(), + }) + .await + .ok(); + } + } HintsUpdateKind::BufferUpdate { buffer_id, buffer_version, excerpts, invalidate_cache, - } => todo!("TODO kb"), - } - - UpdateTaskHandle { - multi_buffer: self.multi_buffer.clone(), - cancellation_tx, - task_finish_rx, + } => { + let mut tasks = excerpts + .into_iter() + .map(|excerpt_id| async move { + // + todo!("TODO kb") + }) + .collect::>(); + while let Some(update) = tasks.next().await { + todo!("TODO kb") + } + } } } } fn spawn_hints_update_loop( hint_updates_rx: smol::channel::Receiver, + update_results_tx: smol::channel::Sender, cx: &mut ViewContext<'_, '_, Editor>, ) { - cx.spawn(|editor, mut cx| async move { - let mut update = None::; - let mut next_update = None::; - loop { - if update.is_none() { - match hint_updates_rx.recv().await { - Ok(first_task) => update = Some(first_task), - Err(smol::channel::RecvError) => return, + cx.background() + .spawn(async move { + let mut update = None::; + let mut next_update = None::; + loop { + if update.is_none() { + match hint_updates_rx.recv().await { + Ok(first_task) => update = Some(first_task), + Err(smol::channel::RecvError) => return, + } } - } - let mut updates_limit = 10; - 'update_merge: loop { - match hint_updates_rx.try_recv() { - Ok(new_update) => { - match update.as_mut() { - Some(update) => match update.merge(new_update) { - Ok(()) => {} - Err(new_update) => { - next_update = Some(new_update); - break 'update_merge; - } - }, - None => update = Some(new_update), - }; + let mut updates_limit = 10; + 'update_merge: loop { + match hint_updates_rx.try_recv() { + Ok(new_update) => { + match update.as_mut() { + Some(update) => match update.merge(new_update) { + Ok(()) => {} + Err(new_update) => { + next_update = Some(new_update); + break 'update_merge; + } + }, + None => update = Some(new_update), + }; - if updates_limit == 0 { - break 'update_merge; + if updates_limit == 0 { + break 'update_merge; + } + updates_limit -= 1; } - updates_limit -= 1; + Err(smol::channel::TryRecvError::Empty) => break 'update_merge, + Err(smol::channel::TryRecvError::Closed) => return, } - Err(smol::channel::TryRecvError::Empty) => break 'update_merge, - Err(smol::channel::TryRecvError::Closed) => return, } - } - - if let Some(update) = update.take() { - let Ok(task_handle) = editor.update(&mut cx, |_, cx| update.spawn(cx)) else { return; }; - while let Ok(update_task_result) = task_handle.task_finish_rx.recv().await { - let Ok(()) = editor.update(&mut cx, |editor, cx| { - let multi_buffer_snapshot = update_task_result.multi_buffer.read(cx).snapshot(cx); - let inlay_hint_cache = &mut editor.inlay_hint_cache; - - if let Some(new_allowed_hint_kinds) = update_task_result.new_allowed_hint_kinds { - inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds; - } - inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { - buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { - excerpt_hints.retain(|(_, hint_id)| !update_task_result.remove_from_cache.contains(hint_id)); - !excerpt_hints.is_empty() - }); - !buffer_hints.hints_per_excerpt.is_empty() - }); - inlay_hint_cache.inlay_hints.retain(|hint_id, _| !update_task_result.remove_from_cache.contains(hint_id)); - - for (new_buffer_id, new_buffer_inlays) in update_task_result.add_to_cache { - let cached_buffer_hints = inlay_hint_cache.hints_in_buffers.entry(new_buffer_id).or_insert_with(|| BufferHints::new(new_buffer_inlays.buffer_version)); - if cached_buffer_hints.buffer_version.changed_since(&new_buffer_inlays.buffer_version) { - continue; - } - for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays.hints_per_excerpt { - let cached_excerpt_hints = cached_buffer_hints.hints_per_excerpt.entry(excerpt_id).or_default(); - for (new_hint_position, new_hint, new_inlay_id) in new_excerpt_inlays { - if let hash_map::Entry::Vacant(v) = inlay_hint_cache.inlay_hints.entry(new_inlay_id) { - v.insert(new_hint); - match cached_excerpt_hints.binary_search_by(|probe| { - new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, (new_hint_position, new_inlay_id)), + if let Some(update) = update.take() { + let (run_tx, run_rx) = smol::channel::unbounded(); + let mut update_handle = std::pin::pin!(update.run(run_tx).fuse()); + loop { + futures::select_biased! { + update_result = run_rx.recv().fuse() => { + match update_result { + Ok(update_result) => { + if let Err(_) = update_results_tx.send(update_result).await { + return } } + Err(_) => break, } } + _ = &mut update_handle => { + while let Ok(update_result) = run_rx.try_recv() { + if let Err(_) = update_results_tx.send(update_result).await { + return + } + } + break + }, } - - let InlaySplice { - to_remove, - to_insert, - } = update_task_result.splice; - editor.splice_inlay_hints(to_remove, to_insert, cx) - }) else { return; }; + } } + update = next_update.take(); } - update = next_update.take(); - } - }) - .detach() + }) + .detach() } -fn update_allowed_hint_kinds( +fn new_allowed_hint_kinds_splice( multi_buffer_snapshot: &MultiBufferSnapshot, current_inlays: Vec, - old_kinds: HashSet>, - new_kinds: HashSet>, - editor: &mut Editor, + hints_cache: &CacheSnapshot, + new_kinds: &HashSet>, ) -> Option { + let old_kinds = &hints_cache.allowed_hint_kinds; if old_kinds == new_kinds { return None; } let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = group_inlays(&multi_buffer_snapshot, current_inlays); - let hints_cache = &editor.inlay_hint_cache; + let mut shown_hints_to_remove = group_inlays(current_inlays); for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers { let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default(); @@ -528,32 +589,6 @@ fn update_allowed_hint_kinds( }) } -fn clean_cache(editor: &mut Editor, current_inlays: Vec) -> Option { - let hints_cache = &mut editor.inlay_hint_cache; - if hints_cache.inlay_hints.is_empty() { - None - } else { - let splice = InlaySplice { - to_remove: current_inlays - .iter() - .filter(|inlay| { - editor - .copilot_state - .suggestion - .as_ref() - .map(|inlay| inlay.id) - != Some(inlay.id) - }) - .map(|inlay| inlay.id) - .collect(), - to_insert: Vec::new(), - }; - hints_cache.inlay_hints.clear(); - hints_cache.hints_in_buffers.clear(); - Some(splice) - } -} - fn allowed_hint_types( inlay_hint_settings: editor_settings::InlayHints, ) -> HashSet> { @@ -649,10 +684,7 @@ fn fetch_queries( }) } -fn group_inlays( - multi_buffer_snapshot: &MultiBufferSnapshot, - inlays: Vec, -) -> HashMap>> { +fn group_inlays(inlays: Vec) -> HashMap>> { inlays.into_iter().fold( HashMap::>>::default(), |mut current_hints, inlay| { @@ -669,168 +701,168 @@ fn group_inlays( ) } -async fn update_hints( - multi_buffer: ModelHandle, - queries: Vec, - current_inlays: Vec, - invalidate_cache: bool, - cx: &mut ViewContext<'_, '_, Editor>, -) -> Option { - let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); - let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; - - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - let mut cache_hints_to_persist: HashMap>)> = - HashMap::default(); - - editor.update(&mut cx, |editor, cx| { - let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); - for (new_buffer_id, new_hints_per_buffer) in new_hints { - let cached_buffer_hints = editor - .inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new(new_hints_per_buffer.buffer_version.clone()) - }); - - let buffer_cache_hints_to_persist = - cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); - if cached_buffer_hints - .buffer_version - .changed_since(&new_hints_per_buffer.buffer_version) - { - buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; - buffer_cache_hints_to_persist.1.extend( - cached_buffer_hints.hints_per_excerpt.iter().map( - |(excerpt_id, excerpt_hints)| { - ( - *excerpt_id, - excerpt_hints.iter().map(|(_, id)| *id).collect(), - ) - }, - ), - ); - continue; - } - - let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); - for (new_excerpt_id, new_hints_per_excerpt) in - new_hints_per_buffer.hints_per_excerpt - { - let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 - .entry(new_excerpt_id) - .or_default(); - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(new_excerpt_id) - .or_default(); - let empty_shown_excerpt_hints = Vec::new(); - let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); - for new_hint in new_hints_per_excerpt { - let new_hint_anchor = multi_buffer_snapshot - .anchor_in_excerpt(new_excerpt_id, new_hint.position); - let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { - new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) => { - let (_, cached_inlay_id) = cached_excerpt_hints[ix]; - let cache_hit = editor - .inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint) - .is_some(); - if cache_hit { - excerpt_cache_hints_to_persist - .insert(cached_inlay_id); - None - } else { - Some(ix) - } - } - Err(ix) => Some(ix), - }; - - let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { - probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) - }) { - Ok(ix) => {{ - let (_, shown_inlay_id) = shown_excerpt_hints[ix]; - let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint).is_some(); - if shown_hint_found { - Some(shown_inlay_id) - } else { - None - } - }}, - Err(_) => None, - }; - - if let Some(insert_ix) = cache_insert_ix { - let hint_id = match shown_inlay_id { - Some(shown_inlay_id) => shown_inlay_id, - None => { - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); - if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) - { - to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); - } - new_hint_id - } - }; - excerpt_cache_hints_to_persist.insert(hint_id); - cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); - editor - .inlay_hint_cache - .inlay_hints - .insert(hint_id, new_hint); - } - } - } - } - - if conflicts_with_cache { - for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { - match cache_hints_to_persist.get(&shown_buffer_id) { - Some(cached_buffer_hints) => { - for (persisted_id, cached_hints) in &cached_buffer_hints.1 { - shown_hints_to_clean.entry(*persisted_id).or_default() - .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); - } - }, - None => {}, - } - to_remove.extend(shown_hints_to_clean.into_iter() - .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); - } - - editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { - let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; - buffer_hints.buffer_version = buffer_hints_to_persist.0; - buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { - let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; - excerpt_hints.retain(|(_, hint_id)| { - let retain = excerpt_hints_to_persist.contains(hint_id); - if !retain { - editor - .inlay_hint_cache - .inlay_hints - .remove(hint_id); - } - retain - }); - !excerpt_hints.is_empty() - }); - !buffer_hints.hints_per_excerpt.is_empty() - }); - } - - Some(InlaySplice { - to_remove, - to_insert, - }) - }) -} +// async fn update_hints( +// multi_buffer: ModelHandle, +// queries: Vec, +// current_inlays: Vec, +// invalidate_cache: bool, +// cx: &mut ViewContext<'_, '_, Editor>, +// ) -> Option { +// let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); +// let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; + +// let mut to_remove = Vec::new(); +// let mut to_insert = Vec::new(); +// let mut cache_hints_to_persist: HashMap>)> = +// HashMap::default(); + +// editor.update(&mut cx, |editor, cx| { +// let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); +// for (new_buffer_id, new_hints_per_buffer) in new_hints { +// let cached_buffer_hints = editor +// .inlay_hint_cache +// .hints_in_buffers +// .entry(new_buffer_id) +// .or_insert_with(|| { +// BufferHints::new(new_hints_per_buffer.buffer_version.clone()) +// }); + +// let buffer_cache_hints_to_persist = +// cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); +// if cached_buffer_hints +// .buffer_version +// .changed_since(&new_hints_per_buffer.buffer_version) +// { +// buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; +// buffer_cache_hints_to_persist.1.extend( +// cached_buffer_hints.hints_per_excerpt.iter().map( +// |(excerpt_id, excerpt_hints)| { +// ( +// *excerpt_id, +// excerpt_hints.iter().map(|(_, id)| *id).collect(), +// ) +// }, +// ), +// ); +// continue; +// } + +// let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); +// for (new_excerpt_id, new_hints_per_excerpt) in +// new_hints_per_buffer.hints_per_excerpt +// { +// let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 +// .entry(new_excerpt_id) +// .or_default(); +// let cached_excerpt_hints = cached_buffer_hints +// .hints_per_excerpt +// .entry(new_excerpt_id) +// .or_default(); +// let empty_shown_excerpt_hints = Vec::new(); +// let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); +// for new_hint in new_hints_per_excerpt { +// let new_hint_anchor = multi_buffer_snapshot +// .anchor_in_excerpt(new_excerpt_id, new_hint.position); +// let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { +// new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) +// }) { +// Ok(ix) => { +// let (_, cached_inlay_id) = cached_excerpt_hints[ix]; +// let cache_hit = editor +// .inlay_hint_cache +// .inlay_hints +// .get(&cached_inlay_id) +// .filter(|cached_hint| cached_hint == &&new_hint) +// .is_some(); +// if cache_hit { +// excerpt_cache_hints_to_persist +// .insert(cached_inlay_id); +// None +// } else { +// Some(ix) +// } +// } +// Err(ix) => Some(ix), +// }; + +// let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { +// probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) +// }) { +// Ok(ix) => {{ +// let (_, shown_inlay_id) = shown_excerpt_hints[ix]; +// let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) +// .filter(|cached_hint| cached_hint == &&new_hint).is_some(); +// if shown_hint_found { +// Some(shown_inlay_id) +// } else { +// None +// } +// }}, +// Err(_) => None, +// }; + +// if let Some(insert_ix) = cache_insert_ix { +// let hint_id = match shown_inlay_id { +// Some(shown_inlay_id) => shown_inlay_id, +// None => { +// let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); +// if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) +// { +// to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); +// } +// new_hint_id +// } +// }; +// excerpt_cache_hints_to_persist.insert(hint_id); +// cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); +// editor +// .inlay_hint_cache +// .inlay_hints +// .insert(hint_id, new_hint); +// } +// } +// } +// } + +// if conflicts_with_cache { +// for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { +// match cache_hints_to_persist.get(&shown_buffer_id) { +// Some(cached_buffer_hints) => { +// for (persisted_id, cached_hints) in &cached_buffer_hints.1 { +// shown_hints_to_clean.entry(*persisted_id).or_default() +// .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); +// } +// }, +// None => {}, +// } +// to_remove.extend(shown_hints_to_clean.into_iter() +// .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); +// } + +// editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { +// let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; +// buffer_hints.buffer_version = buffer_hints_to_persist.0; +// buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { +// let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; +// excerpt_hints.retain(|(_, hint_id)| { +// let retain = excerpt_hints_to_persist.contains(hint_id); +// if !retain { +// editor +// .inlay_hint_cache +// .inlay_hints +// .remove(hint_id); +// } +// retain +// }); +// !excerpt_hints.is_empty() +// }); +// !buffer_hints.hints_per_excerpt.is_empty() +// }); +// } + +// Some(InlaySplice { +// to_remove, +// to_insert, +// }) +// }) +// } From 8d982a6c2d2535fec64757426a53a5e44adb6db3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 17:05:43 +0300 Subject: [PATCH 123/169] Finish modelling --- crates/editor/src/editor.rs | 2 + crates/editor/src/inlay_hint_cache.rs | 659 +++++++++++++------------- 2 files changed, 333 insertions(+), 328 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5e8237b8323c9707f8c27f661ea79c1e56474b99..5c72374ca515762b2dedc1427870fe64bca86c87 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2647,6 +2647,7 @@ impl Editor { vec![new_query], current_inlays, false, + cx, ) } } @@ -2668,6 +2669,7 @@ impl Editor { replacement_queries, current_inlays, true, + cx, ) } }; diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 0d03787d24b5b4f68ea35be03cdc99773b6d1c89..f1080b61c04084b8095aeedcdb299f6c4f6328e5 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,17 +1,17 @@ use std::cmp; use crate::{ - display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, - MultiBufferSnapshot, + display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBufferSnapshot, }; use anyhow::Context; use clock::Global; use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; -use gpui::{ModelHandle, Task, ViewContext}; +use gpui::{Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; -use collections::{hash_map, HashMap, HashSet}; +use collections::{HashMap, HashSet}; +use util::post_inc; #[derive(Debug)] pub struct InlayHintCache { @@ -57,73 +57,136 @@ impl InlayHintCache { ) -> Self { let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded(); let (update_results_tx, update_results_rx) = smol::channel::unbounded(); + spawn_hints_update_loop(hint_updates_rx, update_results_tx, cx); cx.spawn(|editor, mut cx| async move { while let Ok(update_result) = update_results_rx.recv().await { let editor_absent = editor .update(&mut cx, |editor, cx| { let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx); - let inlay_hint_cache = &mut editor.inlay_hint_cache; - if let Some(new_allowed_hint_kinds) = update_result.new_allowed_hint_kinds { - inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds; - } - - inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { - buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { - excerpt_hints.retain(|(_, hint_id)| { - !update_result.remove_from_cache.contains(hint_id) - }); - !excerpt_hints.is_empty() - }); - !buffer_hints.hints_per_excerpt.is_empty() - }); - inlay_hint_cache.inlay_hints.retain(|hint_id, _| { - !update_result.remove_from_cache.contains(hint_id) - }); - - for (new_buffer_id, new_buffer_inlays) in update_result.add_to_cache { - let cached_buffer_hints = inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new(new_buffer_inlays.buffer_version.clone()) - }); - if cached_buffer_hints - .buffer_version - .changed_since(&new_buffer_inlays.buffer_version) - { - continue; - } - for (excerpt_id, new_excerpt_inlays) in - new_buffer_inlays.hints_per_excerpt - { - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(excerpt_id) - .or_default(); - for (new_hint_position, new_hint, new_inlay_id) in - new_excerpt_inlays - { - if let hash_map::Entry::Vacant(v) = - inlay_hint_cache.inlay_hints.entry(new_inlay_id) + if let Some((splice, remove_from_cache)) = match update_result { + UpdateResult::HintQuery { + query, + add_to_cache, + remove_from_cache, + remove_from_visible, + } => editor.buffer().read(cx).buffer(query.buffer_id).and_then( + |buffer| { + if !buffer.read(cx).version.changed_since(&query.buffer_version) { - v.insert(new_hint); - match cached_excerpt_hints.binary_search_by(|probe| { - new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => cached_excerpt_hints - .insert(ix, (new_hint_position, new_inlay_id)), + let mut new_hints_splice = InlaySplice { + to_remove: remove_from_visible, + to_insert: Vec::new(), + }; + for (new_buffer_id, new_buffer_inlays) in add_to_cache { + let cached_buffer_hints = editor + .inlay_hint_cache + .hints_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferHints::new( + new_buffer_inlays.buffer_version.clone(), + ) + }); + if cached_buffer_hints + .buffer_version + .changed_since(&new_buffer_inlays.buffer_version) + { + continue; + } + for (excerpt_id, new_excerpt_inlays) in + new_buffer_inlays.hints_per_excerpt + { + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt + .entry(excerpt_id) + .or_default(); + for (shown_id, new_hint_position, new_hint) in + new_excerpt_inlays + { + let new_inlay_id = match shown_id { + Some(id) => id, + None => { + let new_inlay_id = InlayId(post_inc( + &mut editor.next_inlay_id, + )); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + new_hints_splice.to_insert.push(( + new_inlay_id, + new_hint_position, + new_hint.clone(), + )); + } + new_inlay_id + } + }; + + editor + .inlay_hint_cache + .inlay_hints + .insert(new_inlay_id, new_hint); + match cached_excerpt_hints.binary_search_by( + |probe| { + new_hint_position.cmp( + &probe.0, + &multi_buffer_snapshot, + ) + }, + ) { + Ok(ix) | Err(ix) => cached_excerpt_hints + .insert( + ix, + (new_hint_position, new_inlay_id), + ), + } + } + } } + Some((new_hints_splice, remove_from_cache)) + } else { + None } + }, + ), + UpdateResult::Other { + new_allowed_hint_kinds, + splice, + remove_from_cache, + } => { + if let Some(new_allowed_hint_kinds) = new_allowed_hint_kinds { + editor.inlay_hint_cache.allowed_hint_kinds = + new_allowed_hint_kinds; } + Some((splice, remove_from_cache)) } + } { + editor + .inlay_hint_cache + .hints_in_buffers + .retain(|_, buffer_hints| { + buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { + excerpt_hints.retain(|(_, hint_id)| { + !remove_from_cache.contains(hint_id) + }); + !excerpt_hints.is_empty() + }); + !buffer_hints.hints_per_excerpt.is_empty() + }); + editor + .inlay_hint_cache + .inlay_hints + .retain(|hint_id, _| !remove_from_cache.contains(hint_id)); + + let InlaySplice { + to_remove, + to_insert, + } = splice; + editor.splice_inlay_hints(to_remove, to_insert, cx) } - - let InlaySplice { - to_remove, - to_insert, - } = update_result.splice; - editor.splice_inlay_hints(to_remove, to_insert, cx) }) .is_err(); if editor_absent { @@ -186,6 +249,7 @@ impl InlayHintCache { queries: Vec, current_inlays: Vec, conflicts_invalidate_cache: bool, + cx: &mut ViewContext, ) { let conflicts_with_cache = conflicts_invalidate_cache && queries.iter().any(|update_query| { @@ -230,23 +294,36 @@ impl InlayHintCache { } }) .fold( - HashMap::)>::default(), + HashMap::< + u64, + ( + Global, + HashMap< + ExcerptId, + Task>)>>, + >, + ), + >::default(), |mut queries_per_buffer, new_query| { - let (current_verison, excerpts_to_query) = + let (current_verison, excerpt_queries) = queries_per_buffer.entry(new_query.buffer_id).or_default(); if new_query.buffer_version.changed_since(current_verison) { - *current_verison = new_query.buffer_version; - *excerpts_to_query = vec![new_query.excerpt_id]; + *current_verison = new_query.buffer_version.clone(); + *excerpt_queries = HashMap::from_iter([( + new_query.excerpt_id, + hints_fetch_task(new_query, cx), + )]); } else if !current_verison.changed_since(&new_query.buffer_version) { - excerpts_to_query.push(new_query.excerpt_id); + excerpt_queries + .insert(new_query.excerpt_id, hints_fetch_task(new_query, cx)); } queries_per_buffer }, ); - for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer { + for (queried_buffer, (buffer_version, excerpt_queries)) in queries_per_buffer { self.hint_updates_tx .send_blocking(HintsUpdate { multi_buffer_snapshot: multi_buffer_snapshot.clone(), @@ -256,7 +333,7 @@ impl InlayHintCache { invalidate_cache: conflicts_with_cache, buffer_id: queried_buffer, buffer_version, - excerpts, + excerpt_queries, }, }) .ok(); @@ -295,16 +372,24 @@ enum HintsUpdateKind { BufferUpdate { buffer_id: u64, buffer_version: Global, - excerpts: Vec, + excerpt_queries: + HashMap>)>>>, invalidate_cache: bool, }, } -struct UpdateResult { - splice: InlaySplice, - new_allowed_hint_kinds: Option>>, - remove_from_cache: HashSet, - add_to_cache: HashMap>, +enum UpdateResult { + HintQuery { + query: InlayHintQuery, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: HashMap, Anchor, InlayHint)>>, + }, + Other { + splice: InlaySplice, + new_allowed_hint_kinds: Option>>, + remove_from_cache: HashSet, + }, } impl HintsUpdate { @@ -322,13 +407,13 @@ impl HintsUpdate { HintsUpdateKind::BufferUpdate { buffer_id: old_buffer_id, buffer_version: old_buffer_version, - excerpts: old_excerpts, + excerpt_queries: old_excerpt_queries, .. }, HintsUpdateKind::BufferUpdate { buffer_id: new_buffer_id, buffer_version: new_buffer_version, - excerpts: new_excerpts, + excerpt_queries: new_excerpt_queries, invalidate_cache: new_invalidate_cache, }, ) => { @@ -353,8 +438,7 @@ impl HintsUpdate { .map(|inlay| inlay.id) .collect::>(); if old_inlays == new_inlays { - old_excerpts.extend(new_excerpts.drain(..)); - old_excerpts.dedup(); + old_excerpt_queries.extend(new_excerpt_queries.drain()); return Ok(()); } } @@ -371,7 +455,7 @@ impl HintsUpdate { HintsUpdateKind::Clean => { if !self.cache.inlay_hints.is_empty() || !self.visible_inlays.is_empty() { result_sender - .send(UpdateResult { + .send(UpdateResult::Other { splice: InlaySplice { to_remove: self .visible_inlays @@ -382,7 +466,6 @@ impl HintsUpdate { }, new_allowed_hint_kinds: None, remove_from_cache: self.cache.inlay_hints.keys().copied().collect(), - add_to_cache: HashMap::default(), }) .await .ok(); @@ -396,11 +479,10 @@ impl HintsUpdate { &new, ) { result_sender - .send(UpdateResult { + .send(UpdateResult::Other { splice, new_allowed_hint_kinds: Some(new), remove_from_cache: HashSet::default(), - add_to_cache: HashMap::default(), }) .await .ok(); @@ -408,19 +490,39 @@ impl HintsUpdate { } HintsUpdateKind::BufferUpdate { buffer_id, - buffer_version, - excerpts, + excerpt_queries, invalidate_cache, + .. } => { - let mut tasks = excerpts + let mut task_query = excerpt_queries .into_iter() - .map(|excerpt_id| async move { - // - todo!("TODO kb") + .map(|(excerpt_id, task)| async move { + let task = task.await; + (excerpt_id, task) }) .collect::>(); - while let Some(update) = tasks.next().await { - todo!("TODO kb") + while let Some((excerpt_id, task_result)) = task_query.next().await { + match task_result { + Ok((query, Some(new_hints))) => { + if !new_hints.is_empty() { + if let Some(hint_update_result) = new_excerpt_hints_update_result( + &self.multi_buffer_snapshot, + &self.visible_inlays, + &self.cache, + query, + new_hints, + invalidate_cache, + ) { + result_sender + .send(hint_update_result) + .await + .ok(); + } + } + }, + Ok((_, None)) => {}, + Err(e) => error!("Excerpt {excerpt_id:?} from buffer {buffer_id} failed to update its hints: {e:#}"), + } } } } @@ -514,7 +616,7 @@ fn new_allowed_hint_kinds_splice( let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = group_inlays(current_inlays); + let mut shown_hints_to_remove = group_inlays(¤t_inlays); for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers { let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default(); @@ -589,6 +691,117 @@ fn new_allowed_hint_kinds_splice( }) } +fn new_excerpt_hints_update_result( + multi_buffer_snapshot: &MultiBufferSnapshot, + current_inlays: &[Inlay], + inlay_hint_cache: &CacheSnapshot, + query: InlayHintQuery, + new_excerpt_hints: Vec, + invalidate_cache: bool, +) -> Option { + let mut remove_from_visible = Vec::new(); + let mut remove_from_cache = HashSet::default(); + let mut add_to_cache: HashMap, Anchor, InlayHint)>> = + HashMap::default(); + let mut cache_hints_to_persist: HashSet = HashSet::default(); + + let currently_shown_hints = group_inlays(¤t_inlays); + let empty = Vec::new(); + let cached_excerpt_hints = inlay_hint_cache + .hints_in_buffers + .get(&query.buffer_id) + .map(|buffer_hints| &buffer_hints.hints_per_excerpt) + .and_then(|excerpt_hints_hints| excerpt_hints_hints.get(&query.excerpt_id)) + .unwrap_or(&empty); + let shown_excerpt_hints = currently_shown_hints + .get(&query.buffer_id) + .and_then(|hints| hints.get(&query.excerpt_id)) + .unwrap_or(&empty); + for new_hint in new_excerpt_hints { + let new_hint_anchor = + multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position); + let should_add_to_cache = match cached_excerpt_hints + .binary_search_by(|probe| new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)) + { + Ok(ix) => { + let (_, cached_inlay_id) = cached_excerpt_hints[ix]; + let cache_hit = inlay_hint_cache + .inlay_hints + .get(&cached_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint) + .is_some(); + if cache_hit { + cache_hints_to_persist.insert(cached_inlay_id); + false + } else { + true + } + } + Err(_) => true, + }; + + let shown_inlay_id = match shown_excerpt_hints + .binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)) + { + Ok(ix) => { + let (_, shown_inlay_id) = shown_excerpt_hints[ix]; + let shown_hint_found = inlay_hint_cache + .inlay_hints + .get(&shown_inlay_id) + .filter(|cached_hint| cached_hint == &&new_hint) + .is_some(); + if shown_hint_found { + Some(shown_inlay_id) + } else { + None + } + } + Err(_) => None, + }; + + if should_add_to_cache { + let id_to_add = match shown_inlay_id { + Some(shown_inlay_id) => { + cache_hints_to_persist.insert(shown_inlay_id); + Some(shown_inlay_id) + } + None => None, + }; + add_to_cache + .entry(query.buffer_id) + .or_insert_with(|| BufferHints::new(query.buffer_version.clone())) + .hints_per_excerpt + .entry(query.excerpt_id) + .or_default() + .push((id_to_add, new_hint_anchor, new_hint.clone())); + } + } + + if invalidate_cache { + remove_from_visible.extend( + shown_excerpt_hints + .iter() + .map(|(_, hint_id)| hint_id) + .filter(|hint_id| cache_hints_to_persist.contains(hint_id)) + .copied(), + ); + remove_from_cache.extend( + inlay_hint_cache + .inlay_hints + .keys() + .filter(|cached_inlay_id| cache_hints_to_persist.contains(cached_inlay_id)) + .copied(), + ); + } + + Some(UpdateResult::HintQuery { + query, + remove_from_visible, + remove_from_cache, + add_to_cache, + }) +} + fn allowed_hint_types( inlay_hint_settings: editor_settings::InlayHints, ) -> HashSet> { @@ -605,86 +818,42 @@ fn allowed_hint_types( new_allowed_hint_types } -// TODO kb wrong, query and update the editor separately -fn fetch_queries( - multi_buffer: ModelHandle, - queries: impl Iterator, +fn hints_fetch_task( + query: InlayHintQuery, cx: &mut ViewContext<'_, '_, Editor>, -) -> Task>>> { - let mut inlay_fetch_tasks = Vec::new(); - for query in queries { - let task_multi_buffer = multi_buffer.clone(); - let task = cx.spawn(|editor, mut cx| async move { - let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id)) - else { return anyhow::Ok((query, Some(Vec::new()))) }; - let task = editor - .update(&mut cx, |editor, cx| { - if let Some((_, excerpt_range)) = task_multi_buffer.read(cx) +) -> Task>)>> { + cx.spawn(|editor, mut cx| async move { + let Ok(task) = editor + .update(&mut cx, |editor, cx| { + Some({ + let multi_buffer = editor.buffer().read(cx); + let buffer_handle = multi_buffer.buffer(query.buffer_id)?; + let (_, excerpt_range) = multi_buffer .excerpts_for_buffer(&buffer_handle, cx) .into_iter() - .find(|(excerpt_id, _)| excerpt_id == &query.excerpt_id) - { - editor.project.as_ref().map(|project| { - project.update(cx, |project, cx| { - project.query_inlay_hints_for_buffer( - buffer_handle, - excerpt_range.context, - cx, - ) - }) - }) - } else { - None - } + .find(|(excerpt_id, _)| excerpt_id == &query.excerpt_id)?; + editor.project.as_ref()?.update(cx, |project, cx| { + project.query_inlay_hints_for_buffer( + buffer_handle, + excerpt_range.context, + cx, + ) + }) }) - .context("inlays fetch task spawn")?; - Ok(( - query, - match task { - Some(task) => task.await.context("inlays for buffer task")?, - None => Some(Vec::new()), - }, - )) - }); - - inlay_fetch_tasks.push(task); - } - - cx.spawn(|editor, cx| async move { - let mut inlay_updates: HashMap> = HashMap::default(); - for task_result in futures::future::join_all(inlay_fetch_tasks).await { - match task_result { - Ok((query, Some(response_hints))) => { - let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| { - editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot()) - })? else { continue; }; - let buffer_hints = inlay_updates - .entry(query.buffer_id) - .or_insert_with(|| BufferHints::new(query.buffer_version.clone())); - if buffer_snapshot.version().changed_since(&buffer_hints.buffer_version) { - continue; - } - let cached_excerpt_hints = buffer_hints - .hints_per_excerpt - .entry(query.excerpt_id) - .or_default(); - for inlay in response_hints { - match cached_excerpt_hints.binary_search_by(|probe| { - inlay.position.cmp(&probe.position, &buffer_snapshot) - }) { - Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, inlay), - } - } - } - Ok((_, None)) => {} - Err(e) => error!("Failed to update inlays for buffer: {e:#}"), - } - } - Ok(inlay_updates) + }) else { + return Ok((query, None)); + }; + Ok(( + query, + match task { + Some(task) => task.await.context("inlays for buffer task")?, + None => Some(Vec::new()), + }, + )) }) } -fn group_inlays(inlays: Vec) -> HashMap>> { +fn group_inlays(inlays: &[Inlay]) -> HashMap>> { inlays.into_iter().fold( HashMap::>>::default(), |mut current_hints, inlay| { @@ -700,169 +869,3 @@ fn group_inlays(inlays: Vec) -> HashMap, -// queries: Vec, -// current_inlays: Vec, -// invalidate_cache: bool, -// cx: &mut ViewContext<'_, '_, Editor>, -// ) -> Option { -// let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx); -// let new_hints = fetch_queries_task.await.context("inlay hints fetch")?; - -// let mut to_remove = Vec::new(); -// let mut to_insert = Vec::new(); -// let mut cache_hints_to_persist: HashMap>)> = -// HashMap::default(); - -// editor.update(&mut cx, |editor, cx| { -// let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); -// for (new_buffer_id, new_hints_per_buffer) in new_hints { -// let cached_buffer_hints = editor -// .inlay_hint_cache -// .hints_in_buffers -// .entry(new_buffer_id) -// .or_insert_with(|| { -// BufferHints::new(new_hints_per_buffer.buffer_version.clone()) -// }); - -// let buffer_cache_hints_to_persist = -// cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default())); -// if cached_buffer_hints -// .buffer_version -// .changed_since(&new_hints_per_buffer.buffer_version) -// { -// buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version; -// buffer_cache_hints_to_persist.1.extend( -// cached_buffer_hints.hints_per_excerpt.iter().map( -// |(excerpt_id, excerpt_hints)| { -// ( -// *excerpt_id, -// excerpt_hints.iter().map(|(_, id)| *id).collect(), -// ) -// }, -// ), -// ); -// continue; -// } - -// let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id); -// for (new_excerpt_id, new_hints_per_excerpt) in -// new_hints_per_buffer.hints_per_excerpt -// { -// let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1 -// .entry(new_excerpt_id) -// .or_default(); -// let cached_excerpt_hints = cached_buffer_hints -// .hints_per_excerpt -// .entry(new_excerpt_id) -// .or_default(); -// let empty_shown_excerpt_hints = Vec::new(); -// let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints); -// for new_hint in new_hints_per_excerpt { -// let new_hint_anchor = multi_buffer_snapshot -// .anchor_in_excerpt(new_excerpt_id, new_hint.position); -// let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| { -// new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) -// }) { -// Ok(ix) => { -// let (_, cached_inlay_id) = cached_excerpt_hints[ix]; -// let cache_hit = editor -// .inlay_hint_cache -// .inlay_hints -// .get(&cached_inlay_id) -// .filter(|cached_hint| cached_hint == &&new_hint) -// .is_some(); -// if cache_hit { -// excerpt_cache_hints_to_persist -// .insert(cached_inlay_id); -// None -// } else { -// Some(ix) -// } -// } -// Err(ix) => Some(ix), -// }; - -// let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { -// probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot) -// }) { -// Ok(ix) => {{ -// let (_, shown_inlay_id) = shown_excerpt_hints[ix]; -// let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id) -// .filter(|cached_hint| cached_hint == &&new_hint).is_some(); -// if shown_hint_found { -// Some(shown_inlay_id) -// } else { -// None -// } -// }}, -// Err(_) => None, -// }; - -// if let Some(insert_ix) = cache_insert_ix { -// let hint_id = match shown_inlay_id { -// Some(shown_inlay_id) => shown_inlay_id, -// None => { -// let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); -// if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind) -// { -// to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone())); -// } -// new_hint_id -// } -// }; -// excerpt_cache_hints_to_persist.insert(hint_id); -// cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id)); -// editor -// .inlay_hint_cache -// .inlay_hints -// .insert(hint_id, new_hint); -// } -// } -// } -// } - -// if conflicts_with_cache { -// for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints { -// match cache_hints_to_persist.get(&shown_buffer_id) { -// Some(cached_buffer_hints) => { -// for (persisted_id, cached_hints) in &cached_buffer_hints.1 { -// shown_hints_to_clean.entry(*persisted_id).or_default() -// .retain(|(_, shown_id)| !cached_hints.contains(shown_id)); -// } -// }, -// None => {}, -// } -// to_remove.extend(shown_hints_to_clean.into_iter() -// .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id))); -// } - -// editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| { -// let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; }; -// buffer_hints.buffer_version = buffer_hints_to_persist.0; -// buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| { -// let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; }; -// excerpt_hints.retain(|(_, hint_id)| { -// let retain = excerpt_hints_to_persist.contains(hint_id); -// if !retain { -// editor -// .inlay_hint_cache -// .inlay_hints -// .remove(hint_id); -// } -// retain -// }); -// !excerpt_hints.is_empty() -// }); -// !buffer_hints.hints_per_excerpt.is_empty() -// }); -// } - -// Some(InlaySplice { -// to_remove, -// to_insert, -// }) -// }) -// } From 97e5d405797ec19a0c1d21951c7582eecbfa9eed Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 19:37:54 +0300 Subject: [PATCH 124/169] Add snapshot version to use when avoiding wrong state updates --- crates/editor/src/inlay_hint_cache.rs | 267 ++++++++++++++------------ 1 file changed, 139 insertions(+), 128 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index f1080b61c04084b8095aeedcdb299f6c4f6328e5..e294a0717ea687dfe6af6f7fa17f685c2fbc2d8d 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -15,17 +15,16 @@ use util::post_inc; #[derive(Debug)] pub struct InlayHintCache { - inlay_hints: HashMap, - hints_in_buffers: HashMap>, - allowed_hint_kinds: HashSet>, + snapshot: CacheSnapshot, hint_updates_tx: smol::channel::Sender, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct CacheSnapshot { inlay_hints: HashMap, hints_in_buffers: HashMap>, allowed_hint_kinds: HashSet>, + version: usize, } #[derive(Clone, Debug)] @@ -60,124 +59,118 @@ impl InlayHintCache { spawn_hints_update_loop(hint_updates_rx, update_results_tx, cx); cx.spawn(|editor, mut cx| async move { - while let Ok(update_result) = update_results_rx.recv().await { + while let Ok((cache_version, update_result)) = update_results_rx.recv().await { let editor_absent = editor .update(&mut cx, |editor, cx| { + if editor.inlay_hint_cache.snapshot.version != cache_version { + return; + } let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx); - if let Some((splice, remove_from_cache)) = match update_result { - UpdateResult::HintQuery { - query, - add_to_cache, - remove_from_cache, - remove_from_visible, - } => editor.buffer().read(cx).buffer(query.buffer_id).and_then( - |buffer| { - if !buffer.read(cx).version.changed_since(&query.buffer_version) + if let Some((mut splice, add_to_cache, remove_from_cache)) = + match update_result { + UpdateResult::HintQuery { + query, + add_to_cache, + remove_from_cache, + remove_from_visible, + } => editor.buffer().read(cx).buffer(query.buffer_id).and_then( + |buffer| { + if !buffer + .read(cx) + .version + .changed_since(&query.buffer_version) + { + Some(( + InlaySplice { + to_remove: remove_from_visible, + to_insert: Vec::new(), + }, + add_to_cache, + remove_from_cache, + )) + } else { + None + } + }, + ), + UpdateResult::Other { + new_allowed_hint_kinds, + splice, + remove_from_cache, + } => { + if let Some(new_allowed_hint_kinds) = new_allowed_hint_kinds { + editor.inlay_hint_cache.snapshot.allowed_hint_kinds = + new_allowed_hint_kinds; + } + Some((splice, HashMap::default(), remove_from_cache)) + } + } + { + let inlay_hint_cache = &mut editor.inlay_hint_cache.snapshot; + dbg!(inlay_hint_cache.version,); + inlay_hint_cache.version += 1; + for (new_buffer_id, new_buffer_inlays) in add_to_cache { + let cached_buffer_hints = inlay_hint_cache + .hints_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferHints::new(new_buffer_inlays.buffer_version.clone()) + }); + if cached_buffer_hints + .buffer_version + .changed_since(&new_buffer_inlays.buffer_version) + { + continue; + } + for (excerpt_id, new_excerpt_inlays) in + new_buffer_inlays.hints_per_excerpt + { + let cached_excerpt_hints = cached_buffer_hints + .hints_per_excerpt + .entry(excerpt_id) + .or_default(); + for (shown_id, new_hint_position, new_hint) in + new_excerpt_inlays { - let mut new_hints_splice = InlaySplice { - to_remove: remove_from_visible, - to_insert: Vec::new(), - }; - for (new_buffer_id, new_buffer_inlays) in add_to_cache { - let cached_buffer_hints = editor - .inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new( - new_buffer_inlays.buffer_version.clone(), - ) - }); - if cached_buffer_hints - .buffer_version - .changed_since(&new_buffer_inlays.buffer_version) - { - continue; - } - for (excerpt_id, new_excerpt_inlays) in - new_buffer_inlays.hints_per_excerpt - { - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(excerpt_id) - .or_default(); - for (shown_id, new_hint_position, new_hint) in - new_excerpt_inlays + let new_inlay_id = match shown_id { + Some(id) => id, + None => { + let new_inlay_id = + InlayId(post_inc(&mut editor.next_inlay_id)); + if inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) { - let new_inlay_id = match shown_id { - Some(id) => id, - None => { - let new_inlay_id = InlayId(post_inc( - &mut editor.next_inlay_id, - )); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - new_hints_splice.to_insert.push(( - new_inlay_id, - new_hint_position, - new_hint.clone(), - )); - } - new_inlay_id - } - }; - - editor - .inlay_hint_cache - .inlay_hints - .insert(new_inlay_id, new_hint); - match cached_excerpt_hints.binary_search_by( - |probe| { - new_hint_position.cmp( - &probe.0, - &multi_buffer_snapshot, - ) - }, - ) { - Ok(ix) | Err(ix) => cached_excerpt_hints - .insert( - ix, - (new_hint_position, new_inlay_id), - ), - } + splice.to_insert.push(( + new_inlay_id, + new_hint_position, + new_hint.clone(), + )); } + new_inlay_id } + }; + + inlay_hint_cache.inlay_hints.insert(new_inlay_id, new_hint); + match cached_excerpt_hints.binary_search_by(|probe| { + new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => cached_excerpt_hints + .insert(ix, (new_hint_position, new_inlay_id)), } - Some((new_hints_splice, remove_from_cache)) - } else { - None } - }, - ), - UpdateResult::Other { - new_allowed_hint_kinds, - splice, - remove_from_cache, - } => { - if let Some(new_allowed_hint_kinds) = new_allowed_hint_kinds { - editor.inlay_hint_cache.allowed_hint_kinds = - new_allowed_hint_kinds; } - Some((splice, remove_from_cache)) } - } { - editor - .inlay_hint_cache - .hints_in_buffers - .retain(|_, buffer_hints| { - buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { - excerpt_hints.retain(|(_, hint_id)| { - !remove_from_cache.contains(hint_id) - }); - !excerpt_hints.is_empty() + inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { + buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { + excerpt_hints.retain(|(_, hint_id)| { + !remove_from_cache.contains(hint_id) }); - !buffer_hints.hints_per_excerpt.is_empty() + !excerpt_hints.is_empty() }); - editor - .inlay_hint_cache + !buffer_hints.hints_per_excerpt.is_empty() + }); + inlay_hint_cache .inlay_hints .retain(|hint_id, _| !remove_from_cache.contains(hint_id)); @@ -185,7 +178,10 @@ impl InlayHintCache { to_remove, to_insert, } = splice; - editor.splice_inlay_hints(to_remove, to_insert, cx) + if !to_remove.is_empty() || !to_insert.is_empty() { + dbg!("+++", to_remove.len(), to_insert.len()); + editor.splice_inlay_hints(to_remove, to_insert, cx) + } } }) .is_err(); @@ -196,9 +192,12 @@ impl InlayHintCache { }) .detach(); Self { - allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), - hints_in_buffers: HashMap::default(), - inlay_hints: HashMap::default(), + snapshot: CacheSnapshot { + allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), + hints_in_buffers: HashMap::default(), + inlay_hints: HashMap::default(), + version: 0, + }, hint_updates_tx, } } @@ -210,8 +209,8 @@ impl InlayHintCache { current_inlays: Vec, ) { if !inlay_hint_settings.enabled { - self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if self.inlay_hints.is_empty() { + self.snapshot.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); + if self.snapshot.inlay_hints.is_empty() { return; } else { self.hint_updates_tx @@ -227,7 +226,7 @@ impl InlayHintCache { } let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if new_allowed_hint_kinds == self.allowed_hint_kinds { + if new_allowed_hint_kinds == self.snapshot.allowed_hint_kinds { return; } @@ -253,7 +252,7 @@ impl InlayHintCache { ) { let conflicts_with_cache = conflicts_invalidate_cache && queries.iter().any(|update_query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id) + let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&update_query.buffer_id) else { return false }; if cached_buffer_hints .buffer_version @@ -275,7 +274,7 @@ impl InlayHintCache { let queries_per_buffer = queries .into_iter() .filter_map(|query| { - let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id) + let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&query.buffer_id) else { return Some(query) }; if cached_buffer_hints .buffer_version @@ -343,11 +342,7 @@ impl InlayHintCache { // TODO kb could be big and cloned per symbol input. // Instead, use `Box`/`Arc`/`Rc`? fn snapshot(&self) -> CacheSnapshot { - CacheSnapshot { - inlay_hints: self.inlay_hints.clone(), - hints_in_buffers: self.hints_in_buffers.clone(), - allowed_hint_kinds: self.allowed_hint_kinds.clone(), - } + self.snapshot.clone() } } @@ -364,6 +359,7 @@ struct HintsUpdate { kind: HintsUpdateKind, } +#[derive(Debug)] enum HintsUpdateKind { Clean, AllowedHintKindsChanged { @@ -573,13 +569,15 @@ fn spawn_hints_update_loop( if let Some(update) = update.take() { let (run_tx, run_rx) = smol::channel::unbounded(); + let run_version = update.cache.version; + dbg!(zz, run_version); let mut update_handle = std::pin::pin!(update.run(run_tx).fuse()); loop { futures::select_biased! { update_result = run_rx.recv().fuse() => { match update_result { Ok(update_result) => { - if let Err(_) = update_results_tx.send(update_result).await { + if let Err(_) = update_results_tx.send((run_version, update_result)).await { return } } @@ -588,7 +586,7 @@ fn spawn_hints_update_loop( } _ = &mut update_handle => { while let Ok(update_result) = run_rx.try_recv() { - if let Err(_) = update_results_tx.send(update_result).await { + if let Err(_) = update_results_tx.send((run_version, update_result)).await { return } } @@ -703,7 +701,20 @@ fn new_excerpt_hints_update_result( let mut remove_from_cache = HashSet::default(); let mut add_to_cache: HashMap, Anchor, InlayHint)>> = HashMap::default(); - let mut cache_hints_to_persist: HashSet = HashSet::default(); + let mut cache_hints_to_persist = inlay_hint_cache + .hints_in_buffers + .iter() + .filter(|(buffer_id, _)| **buffer_id != query.buffer_id) + .flat_map(|(_, buffer_hints)| { + buffer_hints + .hints_per_excerpt + .iter() + .filter(|(excerpt_id, _)| **excerpt_id != query.excerpt_id) + .flat_map(|(_, excerpt_hints)| excerpt_hints) + }) + .map(|(_, id)| id) + .copied() + .collect::>(); let currently_shown_hints = group_inlays(¤t_inlays); let empty = Vec::new(); @@ -782,14 +793,14 @@ fn new_excerpt_hints_update_result( shown_excerpt_hints .iter() .map(|(_, hint_id)| hint_id) - .filter(|hint_id| cache_hints_to_persist.contains(hint_id)) + .filter(|hint_id| !cache_hints_to_persist.contains(hint_id)) .copied(), ); remove_from_cache.extend( inlay_hint_cache .inlay_hints .keys() - .filter(|cached_inlay_id| cache_hints_to_persist.contains(cached_inlay_id)) + .filter(|cached_inlay_id| !cache_hints_to_persist.contains(cached_inlay_id)) .copied(), ); } From 31f0f9f7b13c24de57b8c5cc302a30bec2b83ef2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 20 Jun 2023 23:11:57 +0300 Subject: [PATCH 125/169] Forbid extra inlay updates --- crates/editor/src/display_map/inlay_map.rs | 5 +- crates/editor/src/editor.rs | 2 + crates/editor/src/inlay_hint_cache.rs | 225 +++++++++++++-------- crates/lsp/src/lsp.rs | 1 - crates/project/src/lsp_command.rs | 1 - 5 files changed, 141 insertions(+), 93 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index acd26a28f7e5cfb1ac6c63902ff77c033f499a9e..8639f6d091c21db3da897d3eaeca0a28a31d131a 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -355,8 +355,7 @@ impl InlayMap { let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(); let mut buffer_edits_iter = buffer_edits.iter().peekable(); while let Some(buffer_edit) = buffer_edits_iter.next() { - new_transforms - .push_tree(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); + new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { if cursor.end(&()).0 == buffer_edit.old.start { new_transforms.push(Transform::Isomorphic(transform.clone()), &()); @@ -437,7 +436,7 @@ impl InlayMap { } } - new_transforms.push_tree(cursor.suffix(&()), &()); + new_transforms.append(cursor.suffix(&()), &()); if new_transforms.first().is_none() { new_transforms.push(Transform::Isomorphic(Default::default()), &()); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5c72374ca515762b2dedc1427870fe64bca86c87..913b3fec6651788a597aeb569c6c74604a2b2009 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2635,6 +2635,7 @@ impl Editor { Some(InlayHintQuery { buffer_id, buffer_version: buffer.read(cx).version(), + cache_version: self.inlay_hint_cache.version(), excerpt_id, }) } else { @@ -2660,6 +2661,7 @@ impl Editor { InlayHintQuery { buffer_id: buffer.remote_id(), buffer_version: buffer.version(), + cache_version: self.inlay_hint_cache.version(), excerpt_id, } }) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index e294a0717ea687dfe6af6f7fa17f685c2fbc2d8d..b6ab31bf1daa1a48e28233fecd5546d89d6a5da8 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -10,7 +10,7 @@ use gpui::{Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; -use collections::{HashMap, HashSet}; +use collections::{hash_map, HashMap, HashSet}; use util::post_inc; #[derive(Debug)] @@ -37,6 +37,7 @@ struct BufferHints { pub struct InlayHintQuery { pub buffer_id: u64, pub buffer_version: Global, + pub cache_version: usize, pub excerpt_id: ExcerptId, } @@ -107,7 +108,6 @@ impl InlayHintCache { } { let inlay_hint_cache = &mut editor.inlay_hint_cache.snapshot; - dbg!(inlay_hint_cache.version,); inlay_hint_cache.version += 1; for (new_buffer_id, new_buffer_inlays) in add_to_cache { let cached_buffer_hints = inlay_hint_cache @@ -179,7 +179,6 @@ impl InlayHintCache { to_insert, } = splice; if !to_remove.is_empty() || !to_insert.is_empty() { - dbg!("+++", to_remove.len(), to_insert.len()); editor.splice_inlay_hints(to_remove, to_insert, cx) } } @@ -253,7 +252,7 @@ impl InlayHintCache { let conflicts_with_cache = conflicts_invalidate_cache && queries.iter().any(|update_query| { let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&update_query.buffer_id) - else { return false }; + else { return false }; if cached_buffer_hints .buffer_version .changed_since(&update_query.buffer_version) @@ -276,12 +275,6 @@ impl InlayHintCache { .filter_map(|query| { let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&query.buffer_id) else { return Some(query) }; - if cached_buffer_hints - .buffer_version - .changed_since(&query.buffer_version) - { - return None; - } if conflicts_with_cache || !cached_buffer_hints .hints_per_excerpt @@ -295,44 +288,51 @@ impl InlayHintCache { .fold( HashMap::< u64, - ( - Global, - HashMap< - ExcerptId, + HashMap< + ExcerptId, + ( + usize, Task>)>>, - >, - ), + ), + >, >::default(), |mut queries_per_buffer, new_query| { - let (current_verison, excerpt_queries) = - queries_per_buffer.entry(new_query.buffer_id).or_default(); - - if new_query.buffer_version.changed_since(current_verison) { - *current_verison = new_query.buffer_version.clone(); - *excerpt_queries = HashMap::from_iter([( - new_query.excerpt_id, - hints_fetch_task(new_query, cx), - )]); - } else if !current_verison.changed_since(&new_query.buffer_version) { - excerpt_queries - .insert(new_query.excerpt_id, hints_fetch_task(new_query, cx)); - } + match queries_per_buffer + .entry(new_query.buffer_id) + .or_default() + .entry(new_query.excerpt_id) + { + hash_map::Entry::Occupied(mut o) => { + let (old_cache_verison, _) = o.get_mut(); + if *old_cache_verison <= new_query.cache_version { + let _old_task = o.insert(( + new_query.cache_version, + hints_fetch_task(new_query, cx), + )); + } + } + hash_map::Entry::Vacant(v) => { + v.insert((new_query.cache_version, hints_fetch_task(new_query, cx))); + } + }; queries_per_buffer }, ); - for (queried_buffer, (buffer_version, excerpt_queries)) in queries_per_buffer { + for (queried_buffer, excerpt_queries) in queries_per_buffer { self.hint_updates_tx .send_blocking(HintsUpdate { multi_buffer_snapshot: multi_buffer_snapshot.clone(), visible_inlays: current_inlays.clone(), cache: self.snapshot(), kind: HintsUpdateKind::BufferUpdate { - invalidate_cache: conflicts_with_cache, + conflicts_with_cache, buffer_id: queried_buffer, - buffer_version, - excerpt_queries, + excerpt_queries: excerpt_queries + .into_iter() + .map(|(excerpt_id, (_, tasks))| (excerpt_id, tasks)) + .collect(), }, }) .ok(); @@ -344,6 +344,10 @@ impl InlayHintCache { fn snapshot(&self) -> CacheSnapshot { self.snapshot.clone() } + + pub fn version(&self) -> usize { + self.snapshot.version + } } #[derive(Debug, Default)] @@ -367,13 +371,22 @@ enum HintsUpdateKind { }, BufferUpdate { buffer_id: u64, - buffer_version: Global, excerpt_queries: HashMap>)>>>, - invalidate_cache: bool, + conflicts_with_cache: bool, }, } +impl HintsUpdateKind { + fn name(&self) -> &'static str { + match self { + Self::Clean => "Clean", + Self::AllowedHintKindsChanged { .. } => "AllowedHintKindsChanged", + Self::BufferUpdate { .. } => "BufferUpdate", + } + } +} + enum UpdateResult { HintQuery { query: InlayHintQuery, @@ -389,52 +402,45 @@ enum UpdateResult { } impl HintsUpdate { - fn merge(&mut self, mut other: Self) -> Result<(), Self> { - match (&mut self.kind, &mut other.kind) { + fn merge(&mut self, mut new: Self) -> Result<(), Self> { + match (&mut self.kind, &mut new.kind) { (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()), ( HintsUpdateKind::AllowedHintKindsChanged { .. }, HintsUpdateKind::AllowedHintKindsChanged { .. }, ) => { - *self = other; + *self = new; return Ok(()); } ( HintsUpdateKind::BufferUpdate { buffer_id: old_buffer_id, - buffer_version: old_buffer_version, excerpt_queries: old_excerpt_queries, .. }, HintsUpdateKind::BufferUpdate { buffer_id: new_buffer_id, - buffer_version: new_buffer_version, excerpt_queries: new_excerpt_queries, - invalidate_cache: new_invalidate_cache, + conflicts_with_cache: new_conflicts_with_cache, + .. }, ) => { if old_buffer_id == new_buffer_id { - if new_buffer_version.changed_since(old_buffer_version) { - *self = other; - return Ok(()); - } else if old_buffer_version.changed_since(new_buffer_version) { - return Ok(()); - } else if *new_invalidate_cache { - *self = other; - return Ok(()); - } else { - let old_inlays = self - .visible_inlays - .iter() - .map(|inlay| inlay.id) - .collect::>(); - let new_inlays = other - .visible_inlays - .iter() - .map(|inlay| inlay.id) - .collect::>(); - if old_inlays == new_inlays { - old_excerpt_queries.extend(new_excerpt_queries.drain()); + match self.cache.version.cmp(&new.cache.version) { + cmp::Ordering::Less => { + *self = new; + return Ok(()); + } + cmp::Ordering::Equal => { + if *new_conflicts_with_cache { + *self = new; + return Ok(()); + } else { + old_excerpt_queries.extend(new_excerpt_queries.drain()); + return Ok(()); + } + } + cmp::Ordering::Greater => { return Ok(()); } } @@ -443,7 +449,7 @@ impl HintsUpdate { _ => {} } - Err(other) + Err(new) } async fn run(self, result_sender: smol::channel::Sender) { @@ -487,8 +493,7 @@ impl HintsUpdate { HintsUpdateKind::BufferUpdate { buffer_id, excerpt_queries, - invalidate_cache, - .. + conflicts_with_cache, } => { let mut task_query = excerpt_queries .into_iter() @@ -507,7 +512,7 @@ impl HintsUpdate { &self.cache, query, new_hints, - invalidate_cache, + conflicts_with_cache, ) { result_sender .send(hint_update_result) @@ -527,13 +532,15 @@ impl HintsUpdate { fn spawn_hints_update_loop( hint_updates_rx: smol::channel::Receiver, - update_results_tx: smol::channel::Sender, + update_results_tx: smol::channel::Sender<(usize, UpdateResult)>, cx: &mut ViewContext<'_, '_, Editor>, ) { cx.background() .spawn(async move { let mut update = None::; let mut next_update = None::; + let mut latest_cache_versions_queried = HashMap::<&'static str, usize>::default(); + let mut latest_cache_versions_queried_for_excerpts = HashMap::>::default(); loop { if update.is_none() { match hint_updates_rx.recv().await { @@ -567,31 +574,73 @@ fn spawn_hints_update_loop( } } - if let Some(update) = update.take() { - let (run_tx, run_rx) = smol::channel::unbounded(); - let run_version = update.cache.version; - dbg!(zz, run_version); - let mut update_handle = std::pin::pin!(update.run(run_tx).fuse()); - loop { - futures::select_biased! { - update_result = run_rx.recv().fuse() => { - match update_result { - Ok(update_result) => { - if let Err(_) = update_results_tx.send((run_version, update_result)).await { - return - } + if let Some(mut update) = update.take() { + let new_cache_version = update.cache.version; + let should_update = if let HintsUpdateKind::BufferUpdate { buffer_id, excerpt_queries, conflicts_with_cache } = &mut update.kind { + let buffer_cache_versions = latest_cache_versions_queried_for_excerpts.entry(*buffer_id).or_default(); + *excerpt_queries = excerpt_queries.drain().filter(|(excerpt_id, _)| { + match buffer_cache_versions.entry(*excerpt_id) { + hash_map::Entry::Occupied(mut o) => { + let old_version = *o.get(); + match old_version.cmp(&new_cache_version) { + cmp::Ordering::Less => { + o.insert(new_cache_version); + true + }, + cmp::Ordering::Equal => *conflicts_with_cache || update.visible_inlays.is_empty(), + cmp::Ordering::Greater => false, } - Err(_) => break, - } + + }, + hash_map::Entry::Vacant(v) => { + v.insert(new_cache_version); + true + }, } - _ = &mut update_handle => { - while let Ok(update_result) = run_rx.try_recv() { - if let Err(_) = update_results_tx.send((run_version, update_result)).await { - return - } + }).collect(); + + !excerpt_queries.is_empty() + } else { + match latest_cache_versions_queried.entry(update.kind.name()) { + hash_map::Entry::Occupied(mut o) => { + let old_version = *o.get(); + if old_version < new_cache_version { + o.insert(new_cache_version); + true + } else { + false } - break }, + hash_map::Entry::Vacant(v) => { + v.insert(new_cache_version); + true + }, + } + }; + if should_update { + let (run_tx, run_rx) = smol::channel::unbounded(); + let mut update_handle = std::pin::pin!(update.run(run_tx).fuse()); + loop { + futures::select_biased! { + update_result = run_rx.recv().fuse() => { + match update_result { + Ok(update_result) => { + if let Err(_) = update_results_tx.send((new_cache_version, update_result)).await { + return + } + } + Err(_) => break, + } + } + _ = &mut update_handle => { + while let Ok(update_result) = run_rx.try_recv() { + if let Err(_) = update_results_tx.send((new_cache_version, update_result)).await { + return + } + } + break + }, + } } } } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d8e7efe89b5c9ac680c68c84576a2a03e106c734..4dee0caa393f5f8520c3f13607b1cb7ac6e2fd71 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -432,7 +432,6 @@ impl LanguageServer { content_format: Some(vec![MarkupKind::Markdown]), ..Default::default() }), - // TODO kb add the resolution at least inlay_hint: Some(InlayHintClientCapabilities { resolve_support: None, dynamic_registration: Some(false), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index bce9bf0e10f09ede25de1019de53504df7045471..a7b6e08e913ee4b12a987469cfe7db0218482b91 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1798,7 +1798,6 @@ impl LspCommand for InlayHints { lsp::OneOf::Left(enabled) => *enabled, lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities { lsp::InlayHintServerCapabilities::Options(_) => true, - // TODO kb there could be dynamic registrations, resolve options lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false, }, } From 7fddc223cdf434153824f9b66170111cb0376430 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 21 Jun 2023 22:17:47 +0300 Subject: [PATCH 126/169] Move away heavy inlay computations into background tasks --- crates/editor/src/editor.rs | 91 +- crates/editor/src/inlay_hint_cache.rs | 1112 ++++++++----------------- crates/editor/src/scroll.rs | 7 +- crates/project/src/project.rs | 4 +- 4 files changed, 392 insertions(+), 822 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 913b3fec6651788a597aeb569c6c74604a2b2009..3ec00c8d8d4b06456a26d7ef483588f657da55df 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_cache::{InlayHintCache, InlayHintQuery}; +use inlay_hint_cache::{get_update_state, InlayHintCache, InlaySplice}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1196,7 +1196,7 @@ enum GotoDefinitionKind { #[derive(Debug, Copy, Clone)] enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), - Scroll(ScrollAnchor), + Scroll, VisibleExcerptsChange, } @@ -1367,10 +1367,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_cache: InlayHintCache::new( - settings::get::(cx).inlay_hints, - cx, - ), + inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2612,67 +2609,23 @@ impl Editor { { return; } - - let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); - let current_inlays = self - .display_map - .read(cx) - .current_inlays() - .cloned() - .filter(|inlay| Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id)) - .collect(); match reason { - InlayRefreshReason::SettingsChange(new_settings) => self - .inlay_hint_cache - .spawn_settings_update(multi_buffer_snapshot, new_settings, current_inlays), - InlayRefreshReason::Scroll(scrolled_to) => { - if let Some(new_query) = self.excerpt_visible_offsets(cx).into_iter().find_map( - |(buffer, _, excerpt_id)| { - let buffer_id = scrolled_to.anchor.buffer_id?; - if buffer_id == buffer.read(cx).remote_id() - && scrolled_to.anchor.excerpt_id == excerpt_id - { - Some(InlayHintQuery { - buffer_id, - buffer_version: buffer.read(cx).version(), - cache_version: self.inlay_hint_cache.version(), - excerpt_id, - }) - } else { - None - } - }, - ) { - self.inlay_hint_cache.spawn_hints_update( - multi_buffer_snapshot, - vec![new_query], - current_inlays, - false, - cx, - ) + InlayRefreshReason::SettingsChange(new_settings) => { + let update_state = get_update_state(self, cx); + let new_splice = self + .inlay_hint_cache + .update_settings(new_settings, update_state); + if let Some(InlaySplice { + to_remove, + to_insert, + }) = new_splice + { + self.splice_inlay_hints(to_remove, to_insert, cx); } } + InlayRefreshReason::Scroll => self.inlay_hint_cache.spawn_hints_update(false, cx), InlayRefreshReason::VisibleExcerptsChange => { - let replacement_queries = self - .excerpt_visible_offsets(cx) - .into_iter() - .map(|(buffer, _, excerpt_id)| { - let buffer = buffer.read(cx); - InlayHintQuery { - buffer_id: buffer.remote_id(), - buffer_version: buffer.version(), - cache_version: self.inlay_hint_cache.version(), - excerpt_id, - } - }) - .collect::>(); - self.inlay_hint_cache.spawn_hints_update( - multi_buffer_snapshot, - replacement_queries, - current_inlays, - true, - cx, - ) + self.inlay_hint_cache.spawn_hints_update(true, cx) } }; } @@ -2701,13 +2654,13 @@ impl Editor { fn splice_inlay_hints( &self, to_remove: Vec, - to_insert: Vec<(InlayId, Anchor, project::InlayHint)>, + to_insert: Vec<(Anchor, InlayId, project::InlayHint)>, cx: &mut ViewContext, ) { let buffer = self.buffer.read(cx).read(cx); let new_inlays = to_insert .into_iter() - .map(|(id, position, hint)| { + .map(|(position, id, hint)| { let mut text = hint.text(); // TODO kb styling instead? if hint.padding_right { @@ -7298,7 +7251,7 @@ impl Editor { } multi_buffer::Event::ExcerptsRemoved { ids } => { cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); - true + false } multi_buffer::Event::Reparsed => { cx.emit(Event::Reparsed); @@ -7336,7 +7289,11 @@ impl Editor { }; if refresh_inlays { - self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); + if let Some(_project) = self.project.as_ref() { + // TODO kb non-rust buffer can be edited (e.g. settings) and trigger rust updates + // let zz = project.read(cx).language_servers_for_buffer(buffer, cx); + self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); + } } } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b6ab31bf1daa1a48e28233fecd5546d89d6a5da8..08cb84a2b9f7b1eb8f93c38e36191d420754f51e 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -4,8 +4,6 @@ use crate::{ display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBufferSnapshot, }; use anyhow::Context; -use clock::Global; -use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; use gpui::{Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; @@ -13,785 +11,400 @@ use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; use util::post_inc; -#[derive(Debug)] pub struct InlayHintCache { snapshot: CacheSnapshot, - hint_updates_tx: smol::channel::Sender, + update_tasks: HashMap, +} + +struct InlayHintUpdateTask { + version: usize, + _task: Task<()>, } #[derive(Debug, Clone)] struct CacheSnapshot { - inlay_hints: HashMap, - hints_in_buffers: HashMap>, + hints: HashMap, allowed_hint_kinds: HashSet>, version: usize, } -#[derive(Clone, Debug)] -struct BufferHints { - buffer_version: Global, - hints_per_excerpt: HashMap>, +#[derive(Debug, Clone)] +struct ExcerptCachedHints { + version: usize, + hints: Vec<(Anchor, InlayId, InlayHint)>, } -#[derive(Debug)] -pub struct InlayHintQuery { - pub buffer_id: u64, - pub buffer_version: Global, - pub cache_version: usize, - pub excerpt_id: ExcerptId, +#[derive(Clone)] +pub struct HintsUpdateState { + multi_buffer_snapshot: MultiBufferSnapshot, + visible_inlays: Vec, + cache: CacheSnapshot, } -impl BufferHints { - fn new(buffer_version: Global) -> Self { - Self { - buffer_version, - hints_per_excerpt: HashMap::default(), - } - } +#[derive(Debug, Default)] +pub struct InlaySplice { + pub to_remove: Vec, + pub to_insert: Vec<(Anchor, InlayId, InlayHint)>, } -impl InlayHintCache { - pub fn new( - inlay_hint_settings: editor_settings::InlayHints, - cx: &mut ViewContext, - ) -> Self { - let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded(); - let (update_results_tx, update_results_rx) = smol::channel::unbounded(); +#[derive(Debug)] +struct ExcerptHintsUpdate { + excerpt_id: ExcerptId, + cache_version: usize, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: Vec<(Option, Anchor, InlayHint)>, +} - spawn_hints_update_loop(hint_updates_rx, update_results_tx, cx); - cx.spawn(|editor, mut cx| async move { - while let Ok((cache_version, update_result)) = update_results_rx.recv().await { - let editor_absent = editor - .update(&mut cx, |editor, cx| { - if editor.inlay_hint_cache.snapshot.version != cache_version { - return; - } - let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx); - if let Some((mut splice, add_to_cache, remove_from_cache)) = - match update_result { - UpdateResult::HintQuery { - query, - add_to_cache, - remove_from_cache, - remove_from_visible, - } => editor.buffer().read(cx).buffer(query.buffer_id).and_then( - |buffer| { - if !buffer - .read(cx) - .version - .changed_since(&query.buffer_version) - { - Some(( - InlaySplice { - to_remove: remove_from_visible, - to_insert: Vec::new(), - }, - add_to_cache, - remove_from_cache, - )) - } else { - None - } - }, - ), - UpdateResult::Other { - new_allowed_hint_kinds, - splice, - remove_from_cache, - } => { - if let Some(new_allowed_hint_kinds) = new_allowed_hint_kinds { - editor.inlay_hint_cache.snapshot.allowed_hint_kinds = - new_allowed_hint_kinds; - } - Some((splice, HashMap::default(), remove_from_cache)) - } - } - { - let inlay_hint_cache = &mut editor.inlay_hint_cache.snapshot; - inlay_hint_cache.version += 1; - for (new_buffer_id, new_buffer_inlays) in add_to_cache { - let cached_buffer_hints = inlay_hint_cache - .hints_in_buffers - .entry(new_buffer_id) - .or_insert_with(|| { - BufferHints::new(new_buffer_inlays.buffer_version.clone()) - }); - if cached_buffer_hints - .buffer_version - .changed_since(&new_buffer_inlays.buffer_version) - { - continue; - } - for (excerpt_id, new_excerpt_inlays) in - new_buffer_inlays.hints_per_excerpt - { - let cached_excerpt_hints = cached_buffer_hints - .hints_per_excerpt - .entry(excerpt_id) - .or_default(); - for (shown_id, new_hint_position, new_hint) in - new_excerpt_inlays - { - let new_inlay_id = match shown_id { - Some(id) => id, - None => { - let new_inlay_id = - InlayId(post_inc(&mut editor.next_inlay_id)); - if inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - splice.to_insert.push(( - new_inlay_id, - new_hint_position, - new_hint.clone(), - )); - } - new_inlay_id - } - }; - - inlay_hint_cache.inlay_hints.insert(new_inlay_id, new_hint); - match cached_excerpt_hints.binary_search_by(|probe| { - new_hint_position.cmp(&probe.0, &multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => cached_excerpt_hints - .insert(ix, (new_hint_position, new_inlay_id)), - } - } - } - } - inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| { - buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| { - excerpt_hints.retain(|(_, hint_id)| { - !remove_from_cache.contains(hint_id) - }); - !excerpt_hints.is_empty() - }); - !buffer_hints.hints_per_excerpt.is_empty() - }); - inlay_hint_cache - .inlay_hints - .retain(|hint_id, _| !remove_from_cache.contains(hint_id)); - - let InlaySplice { - to_remove, - to_insert, - } = splice; - if !to_remove.is_empty() || !to_insert.is_empty() { - editor.splice_inlay_hints(to_remove, to_insert, cx) - } - } - }) - .is_err(); - if editor_absent { - return; - } - } - }) - .detach(); +impl InlayHintCache { + pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { snapshot: CacheSnapshot { allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), - hints_in_buffers: HashMap::default(), - inlay_hints: HashMap::default(), + hints: HashMap::default(), version: 0, }, - hint_updates_tx, + update_tasks: HashMap::default(), } } - pub fn spawn_settings_update( + pub fn update_settings( &mut self, - multi_buffer_snapshot: MultiBufferSnapshot, inlay_hint_settings: editor_settings::InlayHints, - current_inlays: Vec, - ) { + update_state: HintsUpdateState, + ) -> Option { + let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if !inlay_hint_settings.enabled { - self.snapshot.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if self.snapshot.inlay_hints.is_empty() { - return; + if self.snapshot.hints.is_empty() { + self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; } else { - self.hint_updates_tx - .send_blocking(HintsUpdate { - multi_buffer_snapshot, - cache: self.snapshot(), - visible_inlays: current_inlays, - kind: HintsUpdateKind::Clean, - }) - .ok(); - return; + self.clear(); + self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; + return Some(InlaySplice { + to_remove: update_state + .visible_inlays + .iter() + .map(|inlay| inlay.id) + .collect(), + to_insert: Vec::new(), + }); } + + return None; } - let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if new_allowed_hint_kinds == self.snapshot.allowed_hint_kinds { - return; + return None; } - self.hint_updates_tx - .send_blocking(HintsUpdate { - multi_buffer_snapshot, - cache: self.snapshot(), - visible_inlays: current_inlays, - kind: HintsUpdateKind::AllowedHintKindsChanged { - new: new_allowed_hint_kinds, - }, - }) - .ok(); + let new_splice = new_allowed_hint_kinds_splice(update_state, &new_allowed_hint_kinds); + if new_splice.is_some() { + self.snapshot.version += 1; + self.update_tasks.clear(); + self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; + } + new_splice } - pub fn spawn_hints_update( - &mut self, - multi_buffer_snapshot: MultiBufferSnapshot, - queries: Vec, - current_inlays: Vec, - conflicts_invalidate_cache: bool, - cx: &mut ViewContext, - ) { - let conflicts_with_cache = conflicts_invalidate_cache - && queries.iter().any(|update_query| { - let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&update_query.buffer_id) - else { return false }; - if cached_buffer_hints - .buffer_version - .changed_since(&update_query.buffer_version) - { - false - } else if update_query - .buffer_version - .changed_since(&cached_buffer_hints.buffer_version) - { - true - } else { - cached_buffer_hints - .hints_per_excerpt - .contains_key(&update_query.excerpt_id) - } - }); - - let queries_per_buffer = queries - .into_iter() - .filter_map(|query| { - let Some(cached_buffer_hints) = self.snapshot.hints_in_buffers.get(&query.buffer_id) - else { return Some(query) }; - if conflicts_with_cache - || !cached_buffer_hints - .hints_per_excerpt - .contains_key(&query.excerpt_id) - { - Some(query) - } else { - None - } - }) - .fold( - HashMap::< - u64, - HashMap< - ExcerptId, - ( - usize, - Task>)>>, - ), - >, - >::default(), - |mut queries_per_buffer, new_query| { - match queries_per_buffer - .entry(new_query.buffer_id) - .or_default() - .entry(new_query.excerpt_id) - { - hash_map::Entry::Occupied(mut o) => { - let (old_cache_verison, _) = o.get_mut(); - if *old_cache_verison <= new_query.cache_version { - let _old_task = o.insert(( - new_query.cache_version, - hints_fetch_task(new_query, cx), - )); + pub fn spawn_hints_update(&self, invalidate_cache: bool, cx: &mut ViewContext) { + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + let mut excerpts_to_query = editor + .excerpt_visible_offsets(cx) + .into_iter() + .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer.read(cx).remote_id())) + .collect::>(); + + let update_state = get_update_state(editor, cx); + let update_tasks = &mut editor.inlay_hint_cache.update_tasks; + if invalidate_cache { + update_tasks.retain(|task_excerpt_id, _| { + excerpts_to_query.contains_key(task_excerpt_id) + }); + } + + let cache_version = editor.inlay_hint_cache.snapshot.version; + excerpts_to_query.retain(|visible_excerpt_id, _| { + match update_tasks.entry(*visible_excerpt_id) { + hash_map::Entry::Occupied(o) => { + match o.get().version.cmp(&cache_version) { + cmp::Ordering::Less => true, + cmp::Ordering::Equal => invalidate_cache, + cmp::Ordering::Greater => false, + } } + hash_map::Entry::Vacant(_) => true, } - hash_map::Entry::Vacant(v) => { - v.insert((new_query.cache_version, hints_fetch_task(new_query, cx))); - } - }; - - queries_per_buffer - }, - ); - - for (queried_buffer, excerpt_queries) in queries_per_buffer { - self.hint_updates_tx - .send_blocking(HintsUpdate { - multi_buffer_snapshot: multi_buffer_snapshot.clone(), - visible_inlays: current_inlays.clone(), - cache: self.snapshot(), - kind: HintsUpdateKind::BufferUpdate { - conflicts_with_cache, - buffer_id: queried_buffer, - excerpt_queries: excerpt_queries - .into_iter() - .map(|(excerpt_id, (_, tasks))| (excerpt_id, tasks)) - .collect(), - }, + }); + + for (excerpt_id, buffer_id) in excerpts_to_query { + update_tasks.insert( + excerpt_id, + new_update_task( + buffer_id, + excerpt_id, + cache_version, + update_state.clone(), + invalidate_cache, + cx, + ), + ); + } }) .ok(); - } + }) + .detach(); } - // TODO kb could be big and cloned per symbol input. - // Instead, use `Box`/`Arc`/`Rc`? fn snapshot(&self) -> CacheSnapshot { self.snapshot.clone() } - pub fn version(&self) -> usize { - self.snapshot.version - } -} - -#[derive(Debug, Default)] -struct InlaySplice { - to_remove: Vec, - to_insert: Vec<(InlayId, Anchor, InlayHint)>, -} - -struct HintsUpdate { - multi_buffer_snapshot: MultiBufferSnapshot, - visible_inlays: Vec, - cache: CacheSnapshot, - kind: HintsUpdateKind, -} - -#[derive(Debug)] -enum HintsUpdateKind { - Clean, - AllowedHintKindsChanged { - new: HashSet>, - }, - BufferUpdate { - buffer_id: u64, - excerpt_queries: - HashMap>)>>>, - conflicts_with_cache: bool, - }, -} - -impl HintsUpdateKind { - fn name(&self) -> &'static str { - match self { - Self::Clean => "Clean", - Self::AllowedHintKindsChanged { .. } => "AllowedHintKindsChanged", - Self::BufferUpdate { .. } => "BufferUpdate", - } + fn clear(&mut self) { + self.snapshot.version += 1; + self.update_tasks.clear(); + self.snapshot.hints.clear(); + self.snapshot.allowed_hint_kinds.clear(); } } -enum UpdateResult { - HintQuery { - query: InlayHintQuery, - remove_from_visible: Vec, - remove_from_cache: HashSet, - add_to_cache: HashMap, Anchor, InlayHint)>>, - }, - Other { - splice: InlaySplice, - new_allowed_hint_kinds: Option>>, - remove_from_cache: HashSet, - }, -} - -impl HintsUpdate { - fn merge(&mut self, mut new: Self) -> Result<(), Self> { - match (&mut self.kind, &mut new.kind) { - (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()), - ( - HintsUpdateKind::AllowedHintKindsChanged { .. }, - HintsUpdateKind::AllowedHintKindsChanged { .. }, - ) => { - *self = new; - return Ok(()); - } - ( - HintsUpdateKind::BufferUpdate { - buffer_id: old_buffer_id, - excerpt_queries: old_excerpt_queries, - .. - }, - HintsUpdateKind::BufferUpdate { - buffer_id: new_buffer_id, - excerpt_queries: new_excerpt_queries, - conflicts_with_cache: new_conflicts_with_cache, - .. - }, - ) => { - if old_buffer_id == new_buffer_id { - match self.cache.version.cmp(&new.cache.version) { - cmp::Ordering::Less => { - *self = new; - return Ok(()); - } - cmp::Ordering::Equal => { - if *new_conflicts_with_cache { - *self = new; - return Ok(()); - } else { - old_excerpt_queries.extend(new_excerpt_queries.drain()); - return Ok(()); - } - } - cmp::Ordering::Greater => { - return Ok(()); - } - } - } - } - _ => {} - } - - Err(new) - } - - async fn run(self, result_sender: smol::channel::Sender) { - match self.kind { - HintsUpdateKind::Clean => { - if !self.cache.inlay_hints.is_empty() || !self.visible_inlays.is_empty() { - result_sender - .send(UpdateResult::Other { - splice: InlaySplice { - to_remove: self - .visible_inlays - .iter() - .map(|inlay| inlay.id) - .collect(), - to_insert: Vec::new(), - }, - new_allowed_hint_kinds: None, - remove_from_cache: self.cache.inlay_hints.keys().copied().collect(), - }) - .await - .ok(); - } - } - HintsUpdateKind::AllowedHintKindsChanged { new } => { - if let Some(splice) = new_allowed_hint_kinds_splice( - &self.multi_buffer_snapshot, - self.visible_inlays, - &self.cache, - &new, - ) { - result_sender - .send(UpdateResult::Other { - splice, - new_allowed_hint_kinds: Some(new), - remove_from_cache: HashSet::default(), +fn new_update_task( + buffer_id: u64, + excerpt_id: ExcerptId, + cache_version: usize, + state: HintsUpdateState, + invalidate_cache: bool, + cx: &mut ViewContext<'_, '_, Editor>, +) -> InlayHintUpdateTask { + let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx); + let task_multi_buffer_snapshot = state.multi_buffer_snapshot.clone(); + + InlayHintUpdateTask { + version: cache_version, + _task: cx.spawn(|editor, mut cx| async move { + match hints_fetch_task.await { + Ok(Some(new_hints)) => { + if let Some(new_update) = cx + .background() + .spawn(async move { + new_excerpt_hints_update_result( + state, + excerpt_id, + new_hints, + invalidate_cache, + ) }) .await - .ok(); - } - } - HintsUpdateKind::BufferUpdate { - buffer_id, - excerpt_queries, - conflicts_with_cache, - } => { - let mut task_query = excerpt_queries - .into_iter() - .map(|(excerpt_id, task)| async move { - let task = task.await; - (excerpt_id, task) - }) - .collect::>(); - while let Some((excerpt_id, task_result)) = task_query.next().await { - match task_result { - Ok((query, Some(new_hints))) => { - if !new_hints.is_empty() { - if let Some(hint_update_result) = new_excerpt_hints_update_result( - &self.multi_buffer_snapshot, - &self.visible_inlays, - &self.cache, - query, - new_hints, - conflicts_with_cache, - ) { - result_sender - .send(hint_update_result) - .await - .ok(); - } - } - }, - Ok((_, None)) => {}, - Err(e) => error!("Excerpt {excerpt_id:?} from buffer {buffer_id} failed to update its hints: {e:#}"), - } - } - } - } - } -} - -fn spawn_hints_update_loop( - hint_updates_rx: smol::channel::Receiver, - update_results_tx: smol::channel::Sender<(usize, UpdateResult)>, - cx: &mut ViewContext<'_, '_, Editor>, -) { - cx.background() - .spawn(async move { - let mut update = None::; - let mut next_update = None::; - let mut latest_cache_versions_queried = HashMap::<&'static str, usize>::default(); - let mut latest_cache_versions_queried_for_excerpts = HashMap::>::default(); - loop { - if update.is_none() { - match hint_updates_rx.recv().await { - Ok(first_task) => update = Some(first_task), - Err(smol::channel::RecvError) => return, - } - } - - let mut updates_limit = 10; - 'update_merge: loop { - match hint_updates_rx.try_recv() { - Ok(new_update) => { - match update.as_mut() { - Some(update) => match update.merge(new_update) { - Ok(()) => {} - Err(new_update) => { - next_update = Some(new_update); - break 'update_merge; + { + editor + .update(&mut cx, |editor, cx| { + let cached_excerpt_hints = editor + .inlay_hint_cache + .snapshot + .hints + .entry(new_update.excerpt_id) + .or_insert_with(|| ExcerptCachedHints { + version: new_update.cache_version, + hints: Vec::new(), + }); + match new_update.cache_version.cmp(&cached_excerpt_hints.version) { + cmp::Ordering::Less => return, + cmp::Ordering::Greater | cmp::Ordering::Equal => { + cached_excerpt_hints.version = new_update.cache_version; } - }, - None => update = Some(new_update), - }; - - if updates_limit == 0 { - break 'update_merge; - } - updates_limit -= 1; - } - Err(smol::channel::TryRecvError::Empty) => break 'update_merge, - Err(smol::channel::TryRecvError::Closed) => return, - } - } + } - if let Some(mut update) = update.take() { - let new_cache_version = update.cache.version; - let should_update = if let HintsUpdateKind::BufferUpdate { buffer_id, excerpt_queries, conflicts_with_cache } = &mut update.kind { - let buffer_cache_versions = latest_cache_versions_queried_for_excerpts.entry(*buffer_id).or_default(); - *excerpt_queries = excerpt_queries.drain().filter(|(excerpt_id, _)| { - match buffer_cache_versions.entry(*excerpt_id) { - hash_map::Entry::Occupied(mut o) => { - let old_version = *o.get(); - match old_version.cmp(&new_cache_version) { - cmp::Ordering::Less => { - o.insert(new_cache_version); - true - }, - cmp::Ordering::Equal => *conflicts_with_cache || update.visible_inlays.is_empty(), - cmp::Ordering::Greater => false, - } + editor.inlay_hint_cache.snapshot.version += 1; + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + to_insert: Vec::new(), + }; - }, - hash_map::Entry::Vacant(v) => { - v.insert(new_cache_version); - true - }, - } - }).collect(); - - !excerpt_queries.is_empty() - } else { - match latest_cache_versions_queried.entry(update.kind.name()) { - hash_map::Entry::Occupied(mut o) => { - let old_version = *o.get(); - if old_version < new_cache_version { - o.insert(new_cache_version); - true - } else { - false - } - }, - hash_map::Entry::Vacant(v) => { - v.insert(new_cache_version); - true - }, - } - }; - if should_update { - let (run_tx, run_rx) = smol::channel::unbounded(); - let mut update_handle = std::pin::pin!(update.run(run_tx).fuse()); - loop { - futures::select_biased! { - update_result = run_rx.recv().fuse() => { - match update_result { - Ok(update_result) => { - if let Err(_) = update_results_tx.send((new_cache_version, update_result)).await { - return + for (shown_id, new_hint_position, new_hint) in + new_update.add_to_cache + { + let new_inlay_id = match shown_id { + Some(id) => id, + None => { + let new_inlay_id = + InlayId(post_inc(&mut editor.next_inlay_id)); + if editor + .inlay_hint_cache + .snapshot + .allowed_hint_kinds + .contains(&new_hint.kind) + { + splice.to_insert.push(( + new_hint_position, + new_inlay_id, + new_hint.clone(), + )); } + new_inlay_id } - Err(_) => break, + }; + + match cached_excerpt_hints.hints.binary_search_by(|probe| { + probe.0.cmp(&new_hint_position, &task_multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => cached_excerpt_hints.hints.insert( + ix, + (new_hint_position, new_inlay_id, new_hint), + ), } } - _ = &mut update_handle => { - while let Ok(update_result) = run_rx.try_recv() { - if let Err(_) = update_results_tx.send((new_cache_version, update_result)).await { - return - } - } - break - }, - } - } + editor.inlay_hint_cache.snapshot.hints.retain( + |_, excerpt_hints| { + excerpt_hints.hints.retain(|(_, hint_id, _)| { + !new_update.remove_from_cache.contains(hint_id) + }); + !excerpt_hints.hints.is_empty() + }, + ); + + let InlaySplice { + to_remove, + to_insert, + } = splice; + if !to_remove.is_empty() || !to_insert.is_empty() { + editor.splice_inlay_hints(to_remove, to_insert, cx) + } + }) + .ok(); } } - update = next_update.take(); + Ok(None) => {} + Err(e) => error!( + "Failed to fecth hints for excerpt {excerpt_id:?} in buffer {buffer_id} : {e}" + ), } - }) - .detach() + }), + } +} + +pub fn get_update_state(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> HintsUpdateState { + HintsUpdateState { + visible_inlays: visible_inlay_hints(editor, cx).cloned().collect(), + cache: editor.inlay_hint_cache.snapshot(), + multi_buffer_snapshot: editor.buffer().read(cx).snapshot(cx), + } } fn new_allowed_hint_kinds_splice( - multi_buffer_snapshot: &MultiBufferSnapshot, - current_inlays: Vec, - hints_cache: &CacheSnapshot, + state: HintsUpdateState, new_kinds: &HashSet>, ) -> Option { - let old_kinds = &hints_cache.allowed_hint_kinds; - if old_kinds == new_kinds { + let old_kinds = &state.cache.allowed_hint_kinds; + if new_kinds == old_kinds { return None; } let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = group_inlays(¤t_inlays); - - for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers { - let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default(); - for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt { - let shown_excerpt_hints_to_remove = - shown_buffer_hints_to_remove.entry(*excerpt_id).or_default(); - let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable(); - shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { - loop { - match cached_hints.peek() { - Some((cached_anchor, cached_hint_id)) => { - if cached_hint_id == shown_hint_id { - return !new_kinds.contains( - &hints_cache.inlay_hints.get(&cached_hint_id).unwrap().kind, - ); - } + let mut shown_hints_to_remove = state.visible_inlays.iter().fold( + HashMap::>::default(), + |mut current_hints, inlay| { + current_hints + .entry(inlay.position.excerpt_id) + .or_default() + .push((inlay.position, inlay.id)); + current_hints + }, + ); - match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) { - cmp::Ordering::Less | cmp::Ordering::Equal => { - let maybe_missed_cached_hint = - hints_cache.inlay_hints.get(&cached_hint_id).unwrap(); - let cached_hint_kind = maybe_missed_cached_hint.kind; - if !old_kinds.contains(&cached_hint_kind) - && new_kinds.contains(&cached_hint_kind) - { - to_insert.push(( - *cached_hint_id, - *cached_anchor, - maybe_missed_cached_hint.clone(), - )); - } - cached_hints.next(); - } - cmp::Ordering::Greater => break, + for (excerpt_id, excerpt_cached_hints) in &state.cache.hints { + let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default(); + let mut excerpt_cached_hints = excerpt_cached_hints.hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| loop { + match excerpt_cached_hints.peek() { + Some((cached_anchor, cached_hint_id, cached_hint)) => { + if cached_hint_id == shown_hint_id { + excerpt_cached_hints.next(); + return !new_kinds.contains(&cached_hint.kind); + } + + match cached_anchor.cmp(shown_anchor, &state.multi_buffer_snapshot) { + cmp::Ordering::Less | cmp::Ordering::Equal => { + if !old_kinds.contains(&cached_hint.kind) + && new_kinds.contains(&cached_hint.kind) + { + to_insert.push(( + *cached_anchor, + *cached_hint_id, + cached_hint.clone(), + )); } + excerpt_cached_hints.next(); } - None => return true, + cmp::Ordering::Greater => return true, } } - - match hints_cache.inlay_hints.get(&shown_hint_id) { - Some(shown_hint) => !new_kinds.contains(&shown_hint.kind), - None => true, - } - }); - - for (cached_anchor, cached_hint_id) in cached_hints { - let maybe_missed_cached_hint = - hints_cache.inlay_hints.get(&cached_hint_id).unwrap(); - let cached_hint_kind = maybe_missed_cached_hint.kind; - if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { - to_insert.push(( - *cached_hint_id, - *cached_anchor, - maybe_missed_cached_hint.clone(), - )); - } + None => return true, + } + }); + + for (cached_anchor, cached_hint_id, maybe_missed_cached_hint) in excerpt_cached_hints { + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + to_insert.push(( + *cached_anchor, + *cached_hint_id, + maybe_missed_cached_hint.clone(), + )); } } } to_remove.extend( shown_hints_to_remove - .into_iter() - .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt) - .flat_map(|(_, excerpt_hints)| excerpt_hints) + .into_values() + .flatten() .map(|(_, hint_id)| hint_id), ); - Some(InlaySplice { - to_remove, - to_insert, - }) + if to_remove.is_empty() && to_insert.is_empty() { + None + } else { + Some(InlaySplice { + to_remove, + to_insert, + }) + } } fn new_excerpt_hints_update_result( - multi_buffer_snapshot: &MultiBufferSnapshot, - current_inlays: &[Inlay], - inlay_hint_cache: &CacheSnapshot, - query: InlayHintQuery, + state: HintsUpdateState, + excerpt_id: ExcerptId, new_excerpt_hints: Vec, invalidate_cache: bool, -) -> Option { - let mut remove_from_visible = Vec::new(); - let mut remove_from_cache = HashSet::default(); - let mut add_to_cache: HashMap, Anchor, InlayHint)>> = - HashMap::default(); - let mut cache_hints_to_persist = inlay_hint_cache - .hints_in_buffers +) -> Option { + let mut add_to_cache: Vec<(Option, Anchor, InlayHint)> = Vec::new(); + let shown_excerpt_hints = state + .visible_inlays .iter() - .filter(|(buffer_id, _)| **buffer_id != query.buffer_id) - .flat_map(|(_, buffer_hints)| { - buffer_hints - .hints_per_excerpt - .iter() - .filter(|(excerpt_id, _)| **excerpt_id != query.excerpt_id) - .flat_map(|(_, excerpt_hints)| excerpt_hints) - }) - .map(|(_, id)| id) - .copied() - .collect::>(); - - let currently_shown_hints = group_inlays(¤t_inlays); + .filter(|hint| hint.position.excerpt_id == excerpt_id) + .collect::>(); let empty = Vec::new(); - let cached_excerpt_hints = inlay_hint_cache - .hints_in_buffers - .get(&query.buffer_id) - .map(|buffer_hints| &buffer_hints.hints_per_excerpt) - .and_then(|excerpt_hints_hints| excerpt_hints_hints.get(&query.excerpt_id)) - .unwrap_or(&empty); - let shown_excerpt_hints = currently_shown_hints - .get(&query.buffer_id) - .and_then(|hints| hints.get(&query.excerpt_id)) + let cached_excerpt_hints = state + .cache + .hints + .get(&excerpt_id) + .map(|buffer_excerpts| &buffer_excerpts.hints) .unwrap_or(&empty); + + let mut excerpt_hints_to_persist = HashSet::default(); for new_hint in new_excerpt_hints { - let new_hint_anchor = - multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position); + let new_hint_anchor = state + .multi_buffer_snapshot + .anchor_in_excerpt(excerpt_id, new_hint.position); + // TODO kb use merge sort or something else better let should_add_to_cache = match cached_excerpt_hints - .binary_search_by(|probe| new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)) + .binary_search_by(|probe| new_hint_anchor.cmp(&probe.0, &state.multi_buffer_snapshot)) { Ok(ix) => { - let (_, cached_inlay_id) = cached_excerpt_hints[ix]; - let cache_hit = inlay_hint_cache - .inlay_hints - .get(&cached_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint) - .is_some(); - if cache_hit { - cache_hints_to_persist.insert(cached_inlay_id); + let (_, cached_inlay_id, cached_hint) = &cached_excerpt_hints[ix]; + if cached_hint == &new_hint { + excerpt_hints_to_persist.insert(*cached_inlay_id); false } else { true @@ -800,66 +413,75 @@ fn new_excerpt_hints_update_result( Err(_) => true, }; - let shown_inlay_id = match shown_excerpt_hints - .binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)) - { + let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { + probe + .position + .cmp(&new_hint_anchor, &state.multi_buffer_snapshot) + }) { Ok(ix) => { - let (_, shown_inlay_id) = shown_excerpt_hints[ix]; - let shown_hint_found = inlay_hint_cache - .inlay_hints - .get(&shown_inlay_id) - .filter(|cached_hint| cached_hint == &&new_hint) - .is_some(); - if shown_hint_found { - Some(shown_inlay_id) - } else { - None - } + let shown_hint = &shown_excerpt_hints[ix]; + state + .cache + .hints + .get(&excerpt_id) + .and_then(|excerpt_hints| { + excerpt_hints + .hints + .iter() + .find_map(|(_, cached_id, cached_hint)| { + if cached_id == &shown_hint.id && cached_hint == &new_hint { + Some(cached_id) + } else { + None + } + }) + }) } Err(_) => None, }; if should_add_to_cache { let id_to_add = match shown_inlay_id { - Some(shown_inlay_id) => { - cache_hints_to_persist.insert(shown_inlay_id); + Some(&shown_inlay_id) => { + excerpt_hints_to_persist.insert(shown_inlay_id); Some(shown_inlay_id) } None => None, }; - add_to_cache - .entry(query.buffer_id) - .or_insert_with(|| BufferHints::new(query.buffer_version.clone())) - .hints_per_excerpt - .entry(query.excerpt_id) - .or_default() - .push((id_to_add, new_hint_anchor, new_hint.clone())); + add_to_cache.push((id_to_add, new_hint_anchor, new_hint.clone())); } } + let mut remove_from_visible = Vec::new(); + let mut remove_from_cache = HashSet::default(); if invalidate_cache { remove_from_visible.extend( shown_excerpt_hints .iter() - .map(|(_, hint_id)| hint_id) - .filter(|hint_id| !cache_hints_to_persist.contains(hint_id)) - .copied(), + .map(|inlay_hint| inlay_hint.id) + .filter(|hint_id| !excerpt_hints_to_persist.contains(hint_id)), ); remove_from_cache.extend( - inlay_hint_cache - .inlay_hints - .keys() - .filter(|cached_inlay_id| !cache_hints_to_persist.contains(cached_inlay_id)) - .copied(), + state + .cache + .hints + .values() + .flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(_, id, _)| id)) + .filter(|cached_inlay_id| !excerpt_hints_to_persist.contains(cached_inlay_id)), ); } - Some(UpdateResult::HintQuery { - query, - remove_from_visible, - remove_from_cache, - add_to_cache, - }) + if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() { + None + } else { + Some(ExcerptHintsUpdate { + cache_version: state.cache.version, + excerpt_id, + remove_from_visible, + remove_from_cache, + add_to_cache, + }) + } } fn allowed_hint_types( @@ -879,21 +501,22 @@ fn allowed_hint_types( } fn hints_fetch_task( - query: InlayHintQuery, + buffer_id: u64, + excerpt_id: ExcerptId, cx: &mut ViewContext<'_, '_, Editor>, -) -> Task>)>> { +) -> Task>>> { cx.spawn(|editor, mut cx| async move { let Ok(task) = editor .update(&mut cx, |editor, cx| { Some({ let multi_buffer = editor.buffer().read(cx); - let buffer_handle = multi_buffer.buffer(query.buffer_id)?; + let buffer_handle = multi_buffer.buffer(buffer_id)?; let (_, excerpt_range) = multi_buffer .excerpts_for_buffer(&buffer_handle, cx) .into_iter() - .find(|(excerpt_id, _)| excerpt_id == &query.excerpt_id)?; + .find(|(id, _)| id == &excerpt_id)?; editor.project.as_ref()?.update(cx, |project, cx| { - project.query_inlay_hints_for_buffer( + project.inlay_hints( buffer_handle, excerpt_range.context, cx, @@ -901,31 +524,22 @@ fn hints_fetch_task( }) }) }) else { - return Ok((query, None)); + return Ok(None); }; - Ok(( - query, - match task { - Some(task) => task.await.context("inlays for buffer task")?, - None => Some(Vec::new()), - }, - )) + Ok(match task { + Some(task) => task.await.context("inlays for buffer task")?, + None => Some(Vec::new()), + }) }) } -fn group_inlays(inlays: &[Inlay]) -> HashMap>> { - inlays.into_iter().fold( - HashMap::>>::default(), - |mut current_hints, inlay| { - if let Some(buffer_id) = inlay.position.buffer_id { - current_hints - .entry(buffer_id) - .or_default() - .entry(inlay.position.excerpt_id) - .or_default() - .push((inlay.position, inlay.id)); - } - current_hints - }, - ) +fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( + editor: &'a Editor, + cx: &'b ViewContext<'c, 'd, Editor>, +) -> impl Iterator + 'a { + editor + .display_map + .read(cx) + .current_inlays() + .filter(|inlay| Some(inlay.id) != editor.copilot_state.suggestion.as_ref().map(|h| h.id)) } diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 45ba0bc664e0728118acef680a123e794960c261..35ce06ac4d2bbdef7ff59b6493344b6a23120a2d 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -176,7 +176,7 @@ impl ScrollManager { autoscroll: bool, workspace_id: Option, cx: &mut ViewContext, - ) -> ScrollAnchor { + ) { let (new_anchor, top_row) = if scroll_position.y() <= 0. { ( ScrollAnchor { @@ -205,7 +205,6 @@ impl ScrollManager { }; self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx); - new_anchor } fn set_anchor( @@ -313,7 +312,7 @@ impl Editor { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - let scroll_anchor = self.scroll_manager.set_scroll_position( + self.scroll_manager.set_scroll_position( scroll_position, &map, local, @@ -323,7 +322,7 @@ impl Editor { ); if !self.is_singleton(cx) { - self.refresh_inlays(crate::InlayRefreshReason::Scroll(scroll_anchor), cx); + self.refresh_inlays(crate::InlayRefreshReason::Scroll, cx); } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 89975a57465307c86a4ae2782f8a8f356e9ce2dc..7b868ba54a9b8b2bb63c89cc5bf6aef5e20540b8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4929,7 +4929,7 @@ impl Project { ) } - pub fn query_inlay_hints_for_buffer( + pub fn inlay_hints( &self, buffer_handle: ModelHandle, range: Range, @@ -6768,7 +6768,7 @@ impl Project { .update(&mut cx, |project, cx| { let buffer_end = buffer.read(cx).len(); // TODO kb use cache before querying? - project.query_inlay_hints_for_buffer( + project.inlay_hints( buffer, envelope .payload From 1722d611901affd3aa129026d1ed421f6724fb1f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 00:50:49 +0300 Subject: [PATCH 127/169] Mitigate odd offset calculations --- crates/editor/src/inlay_hint_cache.rs | 55 ++++++++++----------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 08cb84a2b9f7b1eb8f93c38e36191d420754f51e..f5a1309c753414ff0f6206cac4f0b286ba1ceff7 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -53,7 +53,7 @@ struct ExcerptHintsUpdate { cache_version: usize, remove_from_visible: Vec, remove_from_cache: HashSet, - add_to_cache: Vec<(Option, Anchor, InlayHint)>, + add_to_cache: Vec<(Anchor, InlayHint)>, } impl InlayHintCache { @@ -221,29 +221,20 @@ fn new_update_task( to_insert: Vec::new(), }; - for (shown_id, new_hint_position, new_hint) in - new_update.add_to_cache - { - let new_inlay_id = match shown_id { - Some(id) => id, - None => { - let new_inlay_id = - InlayId(post_inc(&mut editor.next_inlay_id)); - if editor - .inlay_hint_cache - .snapshot - .allowed_hint_kinds - .contains(&new_hint.kind) - { - splice.to_insert.push(( - new_hint_position, - new_inlay_id, - new_hint.clone(), - )); - } - new_inlay_id - } - }; + for (new_hint_position, new_hint) in new_update.add_to_cache { + let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if editor + .inlay_hint_cache + .snapshot + .allowed_hint_kinds + .contains(&new_hint.kind) + { + splice.to_insert.push(( + new_hint_position, + new_inlay_id, + new_hint.clone(), + )); + } match cached_excerpt_hints.hints.binary_search_by(|probe| { probe.0.cmp(&new_hint_position, &task_multi_buffer_snapshot) @@ -378,7 +369,7 @@ fn new_excerpt_hints_update_result( new_excerpt_hints: Vec, invalidate_cache: bool, ) -> Option { - let mut add_to_cache: Vec<(Option, Anchor, InlayHint)> = Vec::new(); + let mut add_to_cache: Vec<(Anchor, InlayHint)> = Vec::new(); let shown_excerpt_hints = state .visible_inlays .iter() @@ -394,12 +385,13 @@ fn new_excerpt_hints_update_result( let mut excerpt_hints_to_persist = HashSet::default(); for new_hint in new_excerpt_hints { + // TODO kb this somehow spoils anchors and make them equal for different text anchors. let new_hint_anchor = state .multi_buffer_snapshot .anchor_in_excerpt(excerpt_id, new_hint.position); // TODO kb use merge sort or something else better let should_add_to_cache = match cached_excerpt_hints - .binary_search_by(|probe| new_hint_anchor.cmp(&probe.0, &state.multi_buffer_snapshot)) + .binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &state.multi_buffer_snapshot)) { Ok(ix) => { let (_, cached_inlay_id, cached_hint) = &cached_excerpt_hints[ix]; @@ -441,14 +433,9 @@ fn new_excerpt_hints_update_result( }; if should_add_to_cache { - let id_to_add = match shown_inlay_id { - Some(&shown_inlay_id) => { - excerpt_hints_to_persist.insert(shown_inlay_id); - Some(shown_inlay_id) - } - None => None, - }; - add_to_cache.push((id_to_add, new_hint_anchor, new_hint.clone())); + if shown_inlay_id.is_none() { + add_to_cache.push((new_hint_anchor, new_hint.clone())); + } } } From d6828583d825f75c03d0ce24957b830c91a109c5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 01:28:55 +0300 Subject: [PATCH 128/169] Box the cache for better performance --- crates/editor/src/inlay_hint_cache.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index f5a1309c753414ff0f6206cac4f0b286ba1ceff7..0eb81aa6441dae1c32cf34010b664ff751317206 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -12,7 +12,7 @@ use collections::{hash_map, HashMap, HashSet}; use util::post_inc; pub struct InlayHintCache { - snapshot: CacheSnapshot, + snapshot: Box, update_tasks: HashMap, } @@ -38,7 +38,7 @@ struct ExcerptCachedHints { pub struct HintsUpdateState { multi_buffer_snapshot: MultiBufferSnapshot, visible_inlays: Vec, - cache: CacheSnapshot, + cache: Box, } #[derive(Debug, Default)] @@ -59,11 +59,11 @@ struct ExcerptHintsUpdate { impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { - snapshot: CacheSnapshot { + snapshot: Box::new(CacheSnapshot { allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), hints: HashMap::default(), version: 0, - }, + }), update_tasks: HashMap::default(), } } @@ -157,7 +157,7 @@ impl InlayHintCache { .detach(); } - fn snapshot(&self) -> CacheSnapshot { + fn snapshot(&self) -> Box { self.snapshot.clone() } From d59e91aff2050d85c991fe461c7df853adc488df Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 09:47:12 +0300 Subject: [PATCH 129/169] Insert new hints into cache better --- crates/editor/src/inlay_hint_cache.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 0eb81aa6441dae1c32cf34010b664ff751317206..67dd8d4bd3318b18165b7df4a341e351eae3532b 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -236,15 +236,18 @@ fn new_update_task( )); } - match cached_excerpt_hints.hints.binary_search_by(|probe| { - probe.0.cmp(&new_hint_position, &task_multi_buffer_snapshot) - }) { - Ok(ix) | Err(ix) => cached_excerpt_hints.hints.insert( - ix, - (new_hint_position, new_inlay_id, new_hint), - ), - } + cached_excerpt_hints.hints.push(( + new_hint_position, + new_inlay_id, + new_hint, + )); } + + cached_excerpt_hints.hints.sort_by( + |(position_a, _, _), (position_b, _, _)| { + position_a.cmp(position_b, &task_multi_buffer_snapshot) + }, + ); editor.inlay_hint_cache.snapshot.hints.retain( |_, excerpt_hints| { excerpt_hints.hints.retain(|(_, hint_id, _)| { @@ -389,7 +392,6 @@ fn new_excerpt_hints_update_result( let new_hint_anchor = state .multi_buffer_snapshot .anchor_in_excerpt(excerpt_id, new_hint.position); - // TODO kb use merge sort or something else better let should_add_to_cache = match cached_excerpt_hints .binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &state.multi_buffer_snapshot)) { From cb4b92aa61fbd5c244a6f6e17ee473545a2ad10e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 11:39:10 +0300 Subject: [PATCH 130/169] Simplify hint event management slightly --- crates/editor/src/editor.rs | 58 ++++++++++++--------------- crates/editor/src/inlay_hint_cache.rs | 23 ++++++++++- crates/editor/src/scroll.rs | 2 +- 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3ec00c8d8d4b06456a26d7ef483588f657da55df..93bd073c7a493eb2e674a6745e2e7820ff13217c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1196,8 +1196,8 @@ enum GotoDefinitionKind { #[derive(Debug, Copy, Clone)] enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), - Scroll, - VisibleExcerptsChange, + NewLinesShown, + VisibleLineEdited, } impl Editor { @@ -1311,7 +1311,7 @@ impl Editor { } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); + editor.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); }; })); } @@ -1392,7 +1392,7 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); + this.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); this } @@ -2605,16 +2605,18 @@ impl Editor { } fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { - if self.mode != EditorMode::Full || !settings::get::(cx).inlay_hints.enabled + if self.project.is_none() + || self.mode != EditorMode::Full + || !settings::get::(cx).inlay_hints.enabled { return; } - match reason { + + let invalidate_cache = match reason { InlayRefreshReason::SettingsChange(new_settings) => { - let update_state = get_update_state(self, cx); let new_splice = self .inlay_hint_cache - .update_settings(new_settings, update_state); + .update_settings(new_settings, get_update_state(self, cx)); if let Some(InlaySplice { to_remove, to_insert, @@ -2622,12 +2624,19 @@ impl Editor { { self.splice_inlay_hints(to_remove, to_insert, cx); } + return; } - InlayRefreshReason::Scroll => self.inlay_hint_cache.spawn_hints_update(false, cx), - InlayRefreshReason::VisibleExcerptsChange => { - self.inlay_hint_cache.spawn_hints_update(true, cx) - } + InlayRefreshReason::NewLinesShown => false, + InlayRefreshReason::VisibleLineEdited => true, }; + + let excerpts_to_query = self + .excerpt_visible_offsets(cx) + .into_iter() + .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer.read(cx).remote_id())) + .collect::>(); + self.inlay_hint_cache + .spawn_hints_update(excerpts_to_query, invalidate_cache, cx) } fn excerpt_visible_offsets( @@ -7227,7 +7236,7 @@ impl Editor { event: &multi_buffer::Event, cx: &mut ViewContext, ) { - let refresh_inlays = match event { + match event { multi_buffer::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); @@ -7235,7 +7244,7 @@ impl Editor { self.update_visible_copilot_suggestion(cx); } cx.emit(Event::BufferEdited); - true + self.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); } multi_buffer::Event::ExcerptsAdded { buffer, @@ -7247,54 +7256,37 @@ impl Editor { predecessor: *predecessor, excerpts: excerpts.clone(), }); - true + self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx); } multi_buffer::Event::ExcerptsRemoved { ids } => { cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); - false } multi_buffer::Event::Reparsed => { cx.emit(Event::Reparsed); - false } multi_buffer::Event::DirtyChanged => { cx.emit(Event::DirtyChanged); - false } multi_buffer::Event::Saved => { cx.emit(Event::Saved); - false } multi_buffer::Event::FileHandleChanged => { cx.emit(Event::TitleChanged); - false } multi_buffer::Event::Reloaded => { cx.emit(Event::TitleChanged); - false } multi_buffer::Event::DiffBaseChanged => { cx.emit(Event::DiffBaseChanged); - false } multi_buffer::Event::Closed => { cx.emit(Event::Closed); - false } multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); - false } - _ => false, + _ => {} }; - - if refresh_inlays { - if let Some(_project) = self.project.as_ref() { - // TODO kb non-rust buffer can be edited (e.g. settings) and trigger rust updates - // let zz = project.read(cx).language_servers_for_buffer(buffer, cx); - self.refresh_inlays(InlayRefreshReason::VisibleExcerptsChange, cx); - } - } } fn on_display_map_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 67dd8d4bd3318b18165b7df4a341e351eae3532b..593cd8c6279d329d56513d4047b69d8262baf053 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -106,7 +106,28 @@ impl InlayHintCache { new_splice } - pub fn spawn_hints_update(&self, invalidate_cache: bool, cx: &mut ViewContext) { + pub fn spawn_hints_update( + &mut self, + mut excerpts_to_query: HashMap, + invalidate_cache: bool, + cx: &mut ViewContext, + ) { + let update_tasks = &mut self.update_tasks; + if invalidate_cache { + update_tasks + .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); + } + excerpts_to_query.retain(|visible_excerpt_id, _| { + match update_tasks.entry(*visible_excerpt_id) { + hash_map::Entry::Occupied(o) => match o.get().version.cmp(&self.snapshot.version) { + cmp::Ordering::Less => true, + cmp::Ordering::Equal => invalidate_cache, + cmp::Ordering::Greater => false, + }, + hash_map::Entry::Vacant(_) => true, + } + }); + cx.spawn(|editor, mut cx| async move { editor .update(&mut cx, |editor, cx| { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 35ce06ac4d2bbdef7ff59b6493344b6a23120a2d..b0f329c87fb2d5d803d2d0c2630bcd48499be692 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -322,7 +322,7 @@ impl Editor { ); if !self.is_singleton(cx) { - self.refresh_inlays(crate::InlayRefreshReason::Scroll, cx); + self.refresh_inlays(crate::InlayRefreshReason::NewLinesShown, cx); } } From c61de29c113c61851d81e3d1d1c3f42afbe1639f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 14:55:32 +0300 Subject: [PATCH 131/169] Use proper anchors for remote LSP queries --- crates/project/src/project.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7b868ba54a9b8b2bb63c89cc5bf6aef5e20540b8..5da143f602d206ce9a6fd738ab1dcdcef5797662 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6764,22 +6764,19 @@ impl Project { ) })?; + let start = envelope + .payload + .start + .and_then(deserialize_anchor) + .context("missing range start")?; + let end = envelope + .payload + .end + .and_then(deserialize_anchor) + .context("missing range end")?; let buffer_hints = this .update(&mut cx, |project, cx| { - let buffer_end = buffer.read(cx).len(); - // TODO kb use cache before querying? - project.inlay_hints( - buffer, - envelope - .payload - .start - .map_or(0, |anchor| anchor.offset as usize) - ..envelope - .payload - .end - .map_or(buffer_end, |anchor| anchor.offset as usize), - cx, - ) + project.inlay_hints(buffer, start..end, cx) }) .await .context("inlay hints fetch")? From 781fa0cff49fce51a0dab8708639f5019dbd875b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 17:52:31 +0300 Subject: [PATCH 132/169] Deduplicate LSP requests on multibuffer scroll --- crates/editor/src/editor.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 93bd073c7a493eb2e674a6745e2e7820ff13217c..bb527f41961c5d1f8edeeffa24409654360ec31b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1197,7 +1197,7 @@ enum GotoDefinitionKind { enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), NewLinesShown, - VisibleLineEdited, + ExcerptEdited, } impl Editor { @@ -1311,7 +1311,7 @@ impl Editor { } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); + editor.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); }; })); } @@ -1392,7 +1392,7 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); + this.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); this } @@ -2627,7 +2627,7 @@ impl Editor { return; } InlayRefreshReason::NewLinesShown => false, - InlayRefreshReason::VisibleLineEdited => true, + InlayRefreshReason::ExcerptEdited => true, }; let excerpts_to_query = self @@ -7244,7 +7244,7 @@ impl Editor { self.update_visible_copilot_suggestion(cx); } cx.emit(Event::BufferEdited); - self.refresh_inlays(InlayRefreshReason::VisibleLineEdited, cx); + self.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); } multi_buffer::Event::ExcerptsAdded { buffer, @@ -7256,7 +7256,6 @@ impl Editor { predecessor: *predecessor, excerpts: excerpts.clone(), }); - self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx); } multi_buffer::Event::ExcerptsRemoved { ids } => { cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); From 96a34ad0ee2461357a706212b4d55a0fe228a7af Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 22:07:09 +0300 Subject: [PATCH 133/169] Use text anchors as hint position in hints cache co-authored-by: Max Brunsfeld --- crates/editor/src/editor.rs | 9 +- crates/editor/src/inlay_hint_cache.rs | 214 ++++++++++++-------------- 2 files changed, 106 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb527f41961c5d1f8edeeffa24409654360ec31b..466600a3fca85d7e46ae1fe0245b31c9d067cd1c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2614,9 +2614,12 @@ impl Editor { let invalidate_cache = match reason { InlayRefreshReason::SettingsChange(new_settings) => { - let new_splice = self - .inlay_hint_cache - .update_settings(new_settings, get_update_state(self, cx)); + let new_splice = self.inlay_hint_cache.update_settings( + &self.buffer, + new_settings, + get_update_state(self, cx), + cx, + ); if let Some(InlaySplice { to_remove, to_insert, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 593cd8c6279d329d56513d4047b69d8262baf053..a62706bb7f387ad63cc157cff1582daa3c60d6f8 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,14 +1,13 @@ use std::cmp; -use crate::{ - display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBufferSnapshot, -}; +use crate::{display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer}; use anyhow::Context; -use gpui::{Task, ViewContext}; +use gpui::{ModelHandle, Task, ViewContext}; use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; +use text::BufferSnapshot; use util::post_inc; pub struct InlayHintCache { @@ -21,22 +20,21 @@ struct InlayHintUpdateTask { _task: Task<()>, } -#[derive(Debug, Clone)] +#[derive(Clone)] struct CacheSnapshot { hints: HashMap, allowed_hint_kinds: HashSet>, version: usize, } -#[derive(Debug, Clone)] +#[derive(Clone)] struct ExcerptCachedHints { version: usize, - hints: Vec<(Anchor, InlayId, InlayHint)>, + hints: Vec<(InlayId, InlayHint)>, } #[derive(Clone)] pub struct HintsUpdateState { - multi_buffer_snapshot: MultiBufferSnapshot, visible_inlays: Vec, cache: Box, } @@ -53,7 +51,7 @@ struct ExcerptHintsUpdate { cache_version: usize, remove_from_visible: Vec, remove_from_cache: HashSet, - add_to_cache: Vec<(Anchor, InlayHint)>, + add_to_cache: Vec, } impl InlayHintCache { @@ -70,8 +68,10 @@ impl InlayHintCache { pub fn update_settings( &mut self, + multi_buffer: &ModelHandle, inlay_hint_settings: editor_settings::InlayHints, update_state: HintsUpdateState, + cx: &mut ViewContext, ) -> Option { let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if !inlay_hint_settings.enabled { @@ -97,7 +97,8 @@ impl InlayHintCache { return None; } - let new_splice = new_allowed_hint_kinds_splice(update_state, &new_allowed_hint_kinds); + let new_splice = + new_allowed_hint_kinds_splice(multi_buffer, update_state, &new_allowed_hint_kinds, cx); if new_splice.is_some() { self.snapshot.version += 1; self.update_tasks.clear(); @@ -199,13 +200,20 @@ fn new_update_task( cx: &mut ViewContext<'_, '_, Editor>, ) -> InlayHintUpdateTask { let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx); - let task_multi_buffer_snapshot = state.multi_buffer_snapshot.clone(); - InlayHintUpdateTask { version: cache_version, _task: cx.spawn(|editor, mut cx| async move { + let Some((multi_buffer_snapshot, buffer_snapshot)) = editor + .update(&mut cx, |editor, cx| { + let multi_buffer = editor.buffer().read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let buffer_snapshot = multi_buffer.buffer(buffer_id)?.read(cx).snapshot(); + Some((multi_buffer_snapshot, buffer_snapshot)) + }).ok().flatten() else { return; }; + match hints_fetch_task.await { Ok(Some(new_hints)) => { + let task_buffer_snapshot = buffer_snapshot.clone(); if let Some(new_update) = cx .background() .spawn(async move { @@ -214,6 +222,7 @@ fn new_update_task( excerpt_id, new_hints, invalidate_cache, + &task_buffer_snapshot, ) }) .await @@ -237,12 +246,15 @@ fn new_update_task( } editor.inlay_hint_cache.snapshot.version += 1; + let mut splice = InlaySplice { to_remove: new_update.remove_from_visible, to_insert: Vec::new(), }; - for (new_hint_position, new_hint) in new_update.add_to_cache { + for new_hint in new_update.add_to_cache { + let new_hint_position = multi_buffer_snapshot + .anchor_in_excerpt(excerpt_id, new_hint.position); let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); if editor .inlay_hint_cache @@ -257,21 +269,17 @@ fn new_update_task( )); } - cached_excerpt_hints.hints.push(( - new_hint_position, - new_inlay_id, - new_hint, - )); + cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); } - cached_excerpt_hints.hints.sort_by( - |(position_a, _, _), (position_b, _, _)| { - position_a.cmp(position_b, &task_multi_buffer_snapshot) - }, - ); + cached_excerpt_hints + .hints + .sort_by(|(_, hint_a), (_, hint_b)| { + hint_a.position.cmp(&hint_b.position, &buffer_snapshot) + }); editor.inlay_hint_cache.snapshot.hints.retain( |_, excerpt_hints| { - excerpt_hints.hints.retain(|(_, hint_id, _)| { + excerpt_hints.hints.retain(|(hint_id, _)| { !new_update.remove_from_cache.contains(hint_id) }); !excerpt_hints.hints.is_empty() @@ -302,13 +310,14 @@ pub fn get_update_state(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Hi HintsUpdateState { visible_inlays: visible_inlay_hints(editor, cx).cloned().collect(), cache: editor.inlay_hint_cache.snapshot(), - multi_buffer_snapshot: editor.buffer().read(cx).snapshot(cx), } } fn new_allowed_hint_kinds_splice( + multi_buffer: &ModelHandle, state: HintsUpdateState, new_kinds: &HashSet>, + cx: &mut ViewContext, ) -> Option { let old_kinds = &state.cache.allowed_hint_kinds; if new_kinds == old_kinds { @@ -328,42 +337,56 @@ fn new_allowed_hint_kinds_splice( }, ); + let multi_buffer = multi_buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + for (excerpt_id, excerpt_cached_hints) in &state.cache.hints { let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default(); - let mut excerpt_cached_hints = excerpt_cached_hints.hints.iter().fuse().peekable(); - shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| loop { - match excerpt_cached_hints.peek() { - Some((cached_anchor, cached_hint_id, cached_hint)) => { - if cached_hint_id == shown_hint_id { - excerpt_cached_hints.next(); - return !new_kinds.contains(&cached_hint.kind); - } + let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + let Some(buffer) = shown_anchor + .buffer_id + .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false }; + let buffer_snapshot = buffer.read(cx).snapshot(); + loop { + match excerpt_cache.peek() { + Some((cached_hint_id, cached_hint)) => { + if cached_hint_id == shown_hint_id { + excerpt_cache.next(); + return !new_kinds.contains(&cached_hint.kind); + } - match cached_anchor.cmp(shown_anchor, &state.multi_buffer_snapshot) { - cmp::Ordering::Less | cmp::Ordering::Equal => { - if !old_kinds.contains(&cached_hint.kind) - && new_kinds.contains(&cached_hint.kind) - { - to_insert.push(( - *cached_anchor, - *cached_hint_id, - cached_hint.clone(), - )); + match cached_hint + .position + .cmp(&shown_anchor.text_anchor, &buffer_snapshot) + { + cmp::Ordering::Less | cmp::Ordering::Equal => { + if !old_kinds.contains(&cached_hint.kind) + && new_kinds.contains(&cached_hint.kind) + { + to_insert.push(( + multi_buffer_snapshot + .anchor_in_excerpt(*excerpt_id, cached_hint.position), + *cached_hint_id, + cached_hint.clone(), + )); + } + excerpt_cache.next(); } - excerpt_cached_hints.next(); + cmp::Ordering::Greater => return true, } - cmp::Ordering::Greater => return true, } + None => return true, } - None => return true, } }); - for (cached_anchor, cached_hint_id, maybe_missed_cached_hint) in excerpt_cached_hints { + for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache { let cached_hint_kind = maybe_missed_cached_hint.kind; if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { to_insert.push(( - *cached_anchor, + multi_buffer_snapshot + .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position), *cached_hint_id, maybe_missed_cached_hint.clone(), )); @@ -392,73 +415,34 @@ fn new_excerpt_hints_update_result( excerpt_id: ExcerptId, new_excerpt_hints: Vec, invalidate_cache: bool, + buffer_snapshot: &BufferSnapshot, ) -> Option { - let mut add_to_cache: Vec<(Anchor, InlayHint)> = Vec::new(); - let shown_excerpt_hints = state - .visible_inlays - .iter() - .filter(|hint| hint.position.excerpt_id == excerpt_id) - .collect::>(); - let empty = Vec::new(); - let cached_excerpt_hints = state - .cache - .hints - .get(&excerpt_id) - .map(|buffer_excerpts| &buffer_excerpts.hints) - .unwrap_or(&empty); - - let mut excerpt_hints_to_persist = HashSet::default(); + let mut add_to_cache: Vec = Vec::new(); + let cached_excerpt_hints = state.cache.hints.get(&excerpt_id); + + let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { - // TODO kb this somehow spoils anchors and make them equal for different text anchors. - let new_hint_anchor = state - .multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, new_hint.position); - let should_add_to_cache = match cached_excerpt_hints - .binary_search_by(|probe| probe.0.cmp(&new_hint_anchor, &state.multi_buffer_snapshot)) - { - Ok(ix) => { - let (_, cached_inlay_id, cached_hint) = &cached_excerpt_hints[ix]; - if cached_hint == &new_hint { - excerpt_hints_to_persist.insert(*cached_inlay_id); - false - } else { - true + let missing_from_cache = match cached_excerpt_hints { + Some(cached_excerpt_hints) => { + match cached_excerpt_hints.hints.binary_search_by(|probe| { + probe.1.position.cmp(&new_hint.position, buffer_snapshot) + }) { + Ok(ix) => { + let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix]; + if cached_hint == &new_hint { + excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind); + false + } else { + true + } + } + Err(_) => true, } } - Err(_) => true, + None => true, }; - - let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| { - probe - .position - .cmp(&new_hint_anchor, &state.multi_buffer_snapshot) - }) { - Ok(ix) => { - let shown_hint = &shown_excerpt_hints[ix]; - state - .cache - .hints - .get(&excerpt_id) - .and_then(|excerpt_hints| { - excerpt_hints - .hints - .iter() - .find_map(|(_, cached_id, cached_hint)| { - if cached_id == &shown_hint.id && cached_hint == &new_hint { - Some(cached_id) - } else { - None - } - }) - }) - } - Err(_) => None, - }; - - if should_add_to_cache { - if shown_inlay_id.is_none() { - add_to_cache.push((new_hint_anchor, new_hint.clone())); - } + if missing_from_cache { + add_to_cache.push(new_hint); } } @@ -466,18 +450,20 @@ fn new_excerpt_hints_update_result( let mut remove_from_cache = HashSet::default(); if invalidate_cache { remove_from_visible.extend( - shown_excerpt_hints + state + .visible_inlays .iter() + .filter(|hint| hint.position.excerpt_id == excerpt_id) .map(|inlay_hint| inlay_hint.id) - .filter(|hint_id| !excerpt_hints_to_persist.contains(hint_id)), + .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); remove_from_cache.extend( state .cache .hints .values() - .flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(_, id, _)| id)) - .filter(|cached_inlay_id| !excerpt_hints_to_persist.contains(cached_inlay_id)), + .flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(id, _)| id)) + .filter(|cached_inlay_id| !excerpt_hints_to_persist.contains_key(cached_inlay_id)), ); } From 316e19ce94883de40567fe0f85947916eed3fc35 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 22 Jun 2023 22:11:10 +0300 Subject: [PATCH 134/169] Remove stale cancelled inlay hints workaround --- crates/editor/src/inlay_hint_cache.rs | 2 +- crates/project/src/project.rs | 24 ++++-------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index a62706bb7f387ad63cc157cff1582daa3c60d6f8..9044c6f5095513f26eb68d6a651440445b38d459 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -523,7 +523,7 @@ fn hints_fetch_task( return Ok(None); }; Ok(match task { - Some(task) => task.await.context("inlays for buffer task")?, + Some(task) => Some(task.await.context("inlays for buffer task")?), None => Some(Vec::new()), }) }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5da143f602d206ce9a6fd738ab1dcdcef5797662..a248086494ec0910561ae9123a8cfe0f3fb423f8 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4934,7 +4934,7 @@ impl Project { buffer_handle: ModelHandle, range: Range, cx: &mut ModelContext, - ) -> Task>>> { + ) -> Task>> { let buffer = buffer_handle.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); let range_start = range.start; @@ -4952,11 +4952,7 @@ impl Project { }) .await .context("waiting for inlay hint request range edits")?; - match lsp_request_task.await { - Ok(hints) => Ok(Some(hints)), - Err(e) if is_content_modified_error(&e) => Ok(None), - Err(other_e) => Err(other_e).context("inlay hints LSP request"), - } + lsp_request_task.await.context("inlay hints LSP request") }) } else if let Some(project_id) = self.remote_id() { let client = self.client.clone(); @@ -4981,13 +4977,7 @@ impl Project { ) .await; - match hints_request_result { - Ok(hints) => Ok(Some(hints)), - Err(e) if is_content_modified_error(&e) => Ok(None), - Err(other_err) => { - Err(other_err).context("inlay hints proto response conversion") - } - } + hints_request_result.context("inlay hints proto response conversion") }) } else { Task::ready(Err(anyhow!("project does not have a remote id"))) @@ -6779,8 +6769,7 @@ impl Project { project.inlay_hints(buffer, start..end, cx) }) .await - .context("inlay hints fetch")? - .unwrap_or_default(); + .context("inlay hints fetch")?; Ok(this.update(&mut cx, |project, cx| { InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx) @@ -7855,8 +7844,3 @@ async fn wait_for_loading_buffer( receiver.next().await; } } - -// TODO kb what are better ways? -fn is_content_modified_error(error: &anyhow::Error) -> bool { - format!("{error:#}").contains("content modified") -} From 48982c3036bb76c856d6fa470e7ea1a082a59dd3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 23 Jun 2023 00:11:09 +0300 Subject: [PATCH 135/169] Filter away new hints not in excerpt range --- crates/editor/src/inlay_hint_cache.rs | 139 ++++++++++++++++---------- 1 file changed, 87 insertions(+), 52 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 9044c6f5095513f26eb68d6a651440445b38d459..d1e0a1b933d1c448c219e0df981cc97301fbdb22 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,13 +1,16 @@ -use std::cmp; +use std::{cmp, ops::Range}; -use crate::{display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer}; +use crate::{ + display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, + MultiBufferSnapshot, +}; use anyhow::Context; use gpui::{ModelHandle, Task, ViewContext}; +use language::BufferSnapshot; use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; -use text::BufferSnapshot; use util::post_inc; pub struct InlayHintCache { @@ -39,6 +42,15 @@ pub struct HintsUpdateState { cache: Box, } +#[derive(Debug, Clone)] +struct ExcerptQuery { + buffer_id: u64, + excerpt_id: ExcerptId, + excerpt_range: Range, + cache_version: usize, + invalidate_cache: bool, +} + #[derive(Debug, Default)] pub struct InlaySplice { pub to_remove: Vec, @@ -135,7 +147,7 @@ impl InlayHintCache { let mut excerpts_to_query = editor .excerpt_visible_offsets(cx) .into_iter() - .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer.read(cx).remote_id())) + .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer)) .collect::>(); let update_state = get_update_state(editor, cx); @@ -160,18 +172,41 @@ impl InlayHintCache { } }); - for (excerpt_id, buffer_id) in excerpts_to_query { - update_tasks.insert( - excerpt_id, - new_update_task( - buffer_id, + for (excerpt_id, buffer_handle) in excerpts_to_query { + let (multi_buffer_snapshot, excerpt_range) = + editor.buffer.update(cx, |multi_buffer, cx| { + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + ( + multi_buffer_snapshot, + multi_buffer + .excerpts_for_buffer(&buffer_handle, cx) + .into_iter() + .find(|(id, _)| id == &excerpt_id) + .map(|(_, range)| range.context), + ) + }); + + if let Some(excerpt_range) = excerpt_range { + let buffer = buffer_handle.read(cx); + let buffer_snapshot = buffer.snapshot(); + let query = ExcerptQuery { + buffer_id: buffer.remote_id(), excerpt_id, + excerpt_range, cache_version, - update_state.clone(), invalidate_cache, - cx, - ), - ); + }; + update_tasks.insert( + excerpt_id, + new_update_task( + query, + update_state.clone(), + multi_buffer_snapshot, + buffer_snapshot, + cx, + ), + ); + } } }) .ok(); @@ -192,25 +227,16 @@ impl InlayHintCache { } fn new_update_task( - buffer_id: u64, - excerpt_id: ExcerptId, - cache_version: usize, + query: ExcerptQuery, state: HintsUpdateState, - invalidate_cache: bool, + multi_buffer_snapshot: MultiBufferSnapshot, + buffer_snapshot: BufferSnapshot, cx: &mut ViewContext<'_, '_, Editor>, ) -> InlayHintUpdateTask { - let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx); + let hints_fetch_task = hints_fetch_task(query.clone(), cx); InlayHintUpdateTask { - version: cache_version, + version: query.cache_version, _task: cx.spawn(|editor, mut cx| async move { - let Some((multi_buffer_snapshot, buffer_snapshot)) = editor - .update(&mut cx, |editor, cx| { - let multi_buffer = editor.buffer().read(cx); - let multi_buffer_snapshot = multi_buffer.snapshot(cx); - let buffer_snapshot = multi_buffer.buffer(buffer_id)?.read(cx).snapshot(); - Some((multi_buffer_snapshot, buffer_snapshot)) - }).ok().flatten() else { return; }; - match hints_fetch_task.await { Ok(Some(new_hints)) => { let task_buffer_snapshot = buffer_snapshot.clone(); @@ -219,10 +245,11 @@ fn new_update_task( .spawn(async move { new_excerpt_hints_update_result( state, - excerpt_id, + query.excerpt_id, new_hints, - invalidate_cache, + query.invalidate_cache, &task_buffer_snapshot, + query.excerpt_range, ) }) .await @@ -254,7 +281,7 @@ fn new_update_task( for new_hint in new_update.add_to_cache { let new_hint_position = multi_buffer_snapshot - .anchor_in_excerpt(excerpt_id, new_hint.position); + .anchor_in_excerpt(query.excerpt_id, new_hint.position); let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); if editor .inlay_hint_cache @@ -299,7 +326,8 @@ fn new_update_task( } Ok(None) => {} Err(e) => error!( - "Failed to fecth hints for excerpt {excerpt_id:?} in buffer {buffer_id} : {e}" + "Failed to fecth hints for excerpt {:?} in buffer {} : {}", + query.excerpt_id, query.buffer_id, e ), } }), @@ -416,6 +444,7 @@ fn new_excerpt_hints_update_result( new_excerpt_hints: Vec, invalidate_cache: bool, buffer_snapshot: &BufferSnapshot, + excerpt_range: Range, ) -> Option { let mut add_to_cache: Vec = Vec::new(); let cached_excerpt_hints = state.cache.hints.get(&excerpt_id); @@ -454,6 +483,18 @@ fn new_excerpt_hints_update_result( .visible_inlays .iter() .filter(|hint| hint.position.excerpt_id == excerpt_id) + .filter(|hint| { + excerpt_range + .start + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_le() + }) + .filter(|hint| { + excerpt_range + .end + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_ge() + }) .map(|inlay_hint| inlay_hint.id) .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); @@ -497,34 +538,28 @@ fn allowed_hint_types( } fn hints_fetch_task( - buffer_id: u64, - excerpt_id: ExcerptId, + query: ExcerptQuery, cx: &mut ViewContext<'_, '_, Editor>, ) -> Task>>> { cx.spawn(|editor, mut cx| async move { - let Ok(task) = editor + let task = editor .update(&mut cx, |editor, cx| { - Some({ - let multi_buffer = editor.buffer().read(cx); - let buffer_handle = multi_buffer.buffer(buffer_id)?; - let (_, excerpt_range) = multi_buffer - .excerpts_for_buffer(&buffer_handle, cx) - .into_iter() - .find(|(id, _)| id == &excerpt_id)?; - editor.project.as_ref()?.update(cx, |project, cx| { - project.inlay_hints( - buffer_handle, - excerpt_range.context, - cx, - ) + editor + .buffer() + .read(cx) + .buffer(query.buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.inlay_hints(buffer, query.excerpt_range, cx) + })) }) - }) - }) else { - return Ok(None); - }; + }) + .ok() + .flatten(); Ok(match task { Some(task) => Some(task.await.context("inlays for buffer task")?), - None => Some(Vec::new()), + None => None, }) }) } From f25a09bfd834f712c41c3af615b4cff5efa487b9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 23 Jun 2023 01:07:37 +0300 Subject: [PATCH 136/169] Avoid excessive allocations with Arc around excerpt cached inlays --- crates/editor/src/editor.rs | 6 +- crates/editor/src/inlay_hint_cache.rs | 210 ++++++++++++-------------- 2 files changed, 100 insertions(+), 116 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 466600a3fca85d7e46ae1fe0245b31c9d067cd1c..9b944cb7b8e1d38b67a58c3e27c44c7807472028 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_cache::{get_update_state, InlayHintCache, InlaySplice}; +use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -2617,7 +2617,7 @@ impl Editor { let new_splice = self.inlay_hint_cache.update_settings( &self.buffer, new_settings, - get_update_state(self, cx), + visible_inlay_hints(self, cx).cloned().collect(), cx, ); if let Some(InlaySplice { @@ -2636,7 +2636,7 @@ impl Editor { let excerpts_to_query = self .excerpt_visible_offsets(cx) .into_iter() - .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer.read(cx).remote_id())) + .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer)) .collect::>(); self.inlay_hint_cache .spawn_hints_update(excerpts_to_query, invalidate_cache, cx) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d1e0a1b933d1c448c219e0df981cc97301fbdb22..b3efbe9e879214db4edd3de4e79b5c030fc0d18e 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,4 +1,4 @@ -use std::{cmp, ops::Range}; +use std::{cmp, sync::Arc}; use crate::{ display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, @@ -6,7 +6,7 @@ use crate::{ }; use anyhow::Context; use gpui::{ModelHandle, Task, ViewContext}; -use language::BufferSnapshot; +use language::{Buffer, BufferSnapshot}; use log::error; use project::{InlayHint, InlayHintKind}; @@ -14,7 +14,7 @@ use collections::{hash_map, HashMap, HashSet}; use util::post_inc; pub struct InlayHintCache { - snapshot: Box, + snapshot: CacheSnapshot, update_tasks: HashMap, } @@ -23,30 +23,23 @@ struct InlayHintUpdateTask { _task: Task<()>, } -#[derive(Clone)] struct CacheSnapshot { - hints: HashMap, + hints: HashMap>, allowed_hint_kinds: HashSet>, version: usize, } -#[derive(Clone)] -struct ExcerptCachedHints { +struct CachedExcerptHints { version: usize, hints: Vec<(InlayId, InlayHint)>, } -#[derive(Clone)] -pub struct HintsUpdateState { - visible_inlays: Vec, - cache: Box, -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] struct ExcerptQuery { buffer_id: u64, excerpt_id: ExcerptId, - excerpt_range: Range, + excerpt_range_start: language::Anchor, + excerpt_range_end: language::Anchor, cache_version: usize, invalidate_cache: bool, } @@ -69,11 +62,11 @@ struct ExcerptHintsUpdate { impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { - snapshot: Box::new(CacheSnapshot { + snapshot: CacheSnapshot { allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), hints: HashMap::default(), version: 0, - }), + }, update_tasks: HashMap::default(), } } @@ -82,7 +75,7 @@ impl InlayHintCache { &mut self, multi_buffer: &ModelHandle, inlay_hint_settings: editor_settings::InlayHints, - update_state: HintsUpdateState, + visible_hints: Vec, cx: &mut ViewContext, ) -> Option { let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); @@ -93,11 +86,7 @@ impl InlayHintCache { self.clear(); self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; return Some(InlaySplice { - to_remove: update_state - .visible_inlays - .iter() - .map(|inlay| inlay.id) - .collect(), + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), to_insert: Vec::new(), }); } @@ -109,8 +98,13 @@ impl InlayHintCache { return None; } - let new_splice = - new_allowed_hint_kinds_splice(multi_buffer, update_state, &new_allowed_hint_kinds, cx); + let new_splice = new_allowed_hint_kinds_splice( + &self.snapshot, + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); if new_splice.is_some() { self.snapshot.version += 1; self.update_tasks.clear(); @@ -121,7 +115,7 @@ impl InlayHintCache { pub fn spawn_hints_update( &mut self, - mut excerpts_to_query: HashMap, + mut excerpts_to_query: HashMap>, invalidate_cache: bool, cx: &mut ViewContext, ) { @@ -141,37 +135,27 @@ impl InlayHintCache { } }); + if invalidate_cache { + update_tasks + .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); + } + let cache_version = self.snapshot.version; + excerpts_to_query.retain(|visible_excerpt_id, _| { + match update_tasks.entry(*visible_excerpt_id) { + hash_map::Entry::Occupied(o) => match o.get().version.cmp(&cache_version) { + cmp::Ordering::Less => true, + cmp::Ordering::Equal => invalidate_cache, + cmp::Ordering::Greater => false, + }, + hash_map::Entry::Vacant(_) => true, + } + }); + cx.spawn(|editor, mut cx| async move { editor .update(&mut cx, |editor, cx| { - let mut excerpts_to_query = editor - .excerpt_visible_offsets(cx) - .into_iter() - .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer)) - .collect::>(); - - let update_state = get_update_state(editor, cx); - let update_tasks = &mut editor.inlay_hint_cache.update_tasks; - if invalidate_cache { - update_tasks.retain(|task_excerpt_id, _| { - excerpts_to_query.contains_key(task_excerpt_id) - }); - } - - let cache_version = editor.inlay_hint_cache.snapshot.version; - excerpts_to_query.retain(|visible_excerpt_id, _| { - match update_tasks.entry(*visible_excerpt_id) { - hash_map::Entry::Occupied(o) => { - match o.get().version.cmp(&cache_version) { - cmp::Ordering::Less => true, - cmp::Ordering::Equal => invalidate_cache, - cmp::Ordering::Greater => false, - } - } - hash_map::Entry::Vacant(_) => true, - } - }); - + let visible_hints = + Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); for (excerpt_id, buffer_handle) in excerpts_to_query { let (multi_buffer_snapshot, excerpt_range) = editor.buffer.update(cx, |multi_buffer, cx| { @@ -192,17 +176,25 @@ impl InlayHintCache { let query = ExcerptQuery { buffer_id: buffer.remote_id(), excerpt_id, - excerpt_range, + excerpt_range_start: excerpt_range.start, + excerpt_range_end: excerpt_range.end, cache_version, invalidate_cache, }; - update_tasks.insert( + let cached_excxerpt_hints = editor + .inlay_hint_cache + .snapshot + .hints + .get(&excerpt_id) + .cloned(); + editor.inlay_hint_cache.update_tasks.insert( excerpt_id, new_update_task( query, - update_state.clone(), multi_buffer_snapshot, buffer_snapshot, + Arc::clone(&visible_hints), + cached_excxerpt_hints, cx, ), ); @@ -214,10 +206,6 @@ impl InlayHintCache { .detach(); } - fn snapshot(&self) -> Box { - self.snapshot.clone() - } - fn clear(&mut self) { self.snapshot.version += 1; self.update_tasks.clear(); @@ -228,12 +216,13 @@ impl InlayHintCache { fn new_update_task( query: ExcerptQuery, - state: HintsUpdateState, multi_buffer_snapshot: MultiBufferSnapshot, buffer_snapshot: BufferSnapshot, + visible_hints: Arc>, + cached_excerpt_hints: Option>, cx: &mut ViewContext<'_, '_, Editor>, ) -> InlayHintUpdateTask { - let hints_fetch_task = hints_fetch_task(query.clone(), cx); + let hints_fetch_task = hints_fetch_task(query, cx); InlayHintUpdateTask { version: query.cache_version, _task: cx.spawn(|editor, mut cx| async move { @@ -244,12 +233,11 @@ fn new_update_task( .background() .spawn(async move { new_excerpt_hints_update_result( - state, - query.excerpt_id, + query, new_hints, - query.invalidate_cache, &task_buffer_snapshot, - query.excerpt_range, + cached_excerpt_hints, + &visible_hints, ) }) .await @@ -261,16 +249,24 @@ fn new_update_task( .snapshot .hints .entry(new_update.excerpt_id) - .or_insert_with(|| ExcerptCachedHints { - version: new_update.cache_version, - hints: Vec::new(), + .or_insert_with(|| { + Arc::new(CachedExcerptHints { + version: new_update.cache_version, + hints: Vec::new(), + }) }); + let cached_excerpt_hints = Arc::get_mut(cached_excerpt_hints) + .expect("Cached excerot hints were dropped with the task"); + match new_update.cache_version.cmp(&cached_excerpt_hints.version) { cmp::Ordering::Less => return, cmp::Ordering::Greater | cmp::Ordering::Equal => { cached_excerpt_hints.version = new_update.cache_version; } } + cached_excerpt_hints.hints.retain(|(hint_id, _)| { + !new_update.remove_from_cache.contains(hint_id) + }); editor.inlay_hint_cache.snapshot.version += 1; @@ -304,14 +300,6 @@ fn new_update_task( .sort_by(|(_, hint_a), (_, hint_b)| { hint_a.position.cmp(&hint_b.position, &buffer_snapshot) }); - editor.inlay_hint_cache.snapshot.hints.retain( - |_, excerpt_hints| { - excerpt_hints.hints.retain(|(hint_id, _)| { - !new_update.remove_from_cache.contains(hint_id) - }); - !excerpt_hints.hints.is_empty() - }, - ); let InlaySplice { to_remove, @@ -334,27 +322,21 @@ fn new_update_task( } } -pub fn get_update_state(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> HintsUpdateState { - HintsUpdateState { - visible_inlays: visible_inlay_hints(editor, cx).cloned().collect(), - cache: editor.inlay_hint_cache.snapshot(), - } -} - fn new_allowed_hint_kinds_splice( + cache: &CacheSnapshot, multi_buffer: &ModelHandle, - state: HintsUpdateState, + visible_hints: &[Inlay], new_kinds: &HashSet>, cx: &mut ViewContext, ) -> Option { - let old_kinds = &state.cache.allowed_hint_kinds; + let old_kinds = &cache.allowed_hint_kinds; if new_kinds == old_kinds { return None; } let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = state.visible_inlays.iter().fold( + let mut shown_hints_to_remove = visible_hints.iter().fold( HashMap::>::default(), |mut current_hints, inlay| { current_hints @@ -368,7 +350,7 @@ fn new_allowed_hint_kinds_splice( let multi_buffer = multi_buffer.read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); - for (excerpt_id, excerpt_cached_hints) in &state.cache.hints { + for (excerpt_id, excerpt_cached_hints) in &cache.hints { let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default(); let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable(); shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { @@ -439,19 +421,17 @@ fn new_allowed_hint_kinds_splice( } fn new_excerpt_hints_update_result( - state: HintsUpdateState, - excerpt_id: ExcerptId, + query: ExcerptQuery, new_excerpt_hints: Vec, - invalidate_cache: bool, buffer_snapshot: &BufferSnapshot, - excerpt_range: Range, + cached_excerpt_hints: Option>, + visible_hints: &[Inlay], ) -> Option { let mut add_to_cache: Vec = Vec::new(); - let cached_excerpt_hints = state.cache.hints.get(&excerpt_id); let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { - let missing_from_cache = match cached_excerpt_hints { + let missing_from_cache = match &cached_excerpt_hints { Some(cached_excerpt_hints) => { match cached_excerpt_hints.hints.binary_search_by(|probe| { probe.1.position.cmp(&new_hint.position, buffer_snapshot) @@ -477,21 +457,20 @@ fn new_excerpt_hints_update_result( let mut remove_from_visible = Vec::new(); let mut remove_from_cache = HashSet::default(); - if invalidate_cache { + if query.invalidate_cache { remove_from_visible.extend( - state - .visible_inlays + visible_hints .iter() - .filter(|hint| hint.position.excerpt_id == excerpt_id) + .filter(|hint| hint.position.excerpt_id == query.excerpt_id) .filter(|hint| { - excerpt_range - .start + query + .excerpt_range_start .cmp(&hint.position.text_anchor, buffer_snapshot) .is_le() }) .filter(|hint| { - excerpt_range - .end + query + .excerpt_range_end .cmp(&hint.position.text_anchor, buffer_snapshot) .is_ge() }) @@ -499,12 +478,13 @@ fn new_excerpt_hints_update_result( .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); remove_from_cache.extend( - state - .cache - .hints - .values() - .flat_map(|excerpt_hints| excerpt_hints.hints.iter().map(|(id, _)| id)) - .filter(|cached_inlay_id| !excerpt_hints_to_persist.contains_key(cached_inlay_id)), + cached_excerpt_hints + .iter() + .flat_map(|excerpt_hints| excerpt_hints.hints.iter()) + .filter(|(cached_inlay_id, _)| { + !excerpt_hints_to_persist.contains_key(cached_inlay_id) + }) + .map(|(cached_inlay_id, _)| *cached_inlay_id), ); } @@ -512,8 +492,8 @@ fn new_excerpt_hints_update_result( None } else { Some(ExcerptHintsUpdate { - cache_version: state.cache.version, - excerpt_id, + cache_version: query.cache_version, + excerpt_id: query.excerpt_id, remove_from_visible, remove_from_cache, add_to_cache, @@ -551,7 +531,11 @@ fn hints_fetch_task( .and_then(|buffer| { let project = editor.project.as_ref()?; Some(project.update(cx, |project, cx| { - project.inlay_hints(buffer, query.excerpt_range, cx) + project.inlay_hints( + buffer, + query.excerpt_range_start..query.excerpt_range_end, + cx, + ) })) }) }) @@ -564,7 +548,7 @@ fn hints_fetch_task( }) } -fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( +pub fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( editor: &'a Editor, cx: &'b ViewContext<'c, 'd, Editor>, ) -> impl Iterator + 'a { From ba3d1e4dbacb5e75cd5c862f3d78a42596a02d8f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 23 Jun 2023 01:27:58 +0300 Subject: [PATCH 137/169] Deduplicate inlay hints queries with buffer versions --- crates/editor/src/display_map.rs | 1 - crates/editor/src/editor.rs | 10 ++++--- crates/editor/src/inlay_hint_cache.rs | 42 +++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 16d44fbacfb9aaa806ed018ed48f7dbc3623f075..b117120e81c5921ae9a13bc2da8697742660419b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -255,7 +255,6 @@ impl DisplayMap { if to_remove.is_empty() && to_insert.is_empty() { return; } - let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9b944cb7b8e1d38b67a58c3e27c44c7807472028..288ee516d106de78a1d662b46dc00c8e7173ba32 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -54,7 +54,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice}; +use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice, InvalidationStrategy}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -1198,6 +1198,7 @@ enum InlayRefreshReason { SettingsChange(editor_settings::InlayHints), NewLinesShown, ExcerptEdited, + RefreshRequested, } impl Editor { @@ -1311,7 +1312,7 @@ impl Editor { } project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { if let project::Event::RefreshInlays = event { - editor.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); + editor.refresh_inlays(InlayRefreshReason::RefreshRequested, cx); }; })); } @@ -2629,8 +2630,9 @@ impl Editor { } return; } - InlayRefreshReason::NewLinesShown => false, - InlayRefreshReason::ExcerptEdited => true, + InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, + InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict, + InlayRefreshReason::RefreshRequested => InvalidationStrategy::All, }; let excerpts_to_query = self diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b3efbe9e879214db4edd3de4e79b5c030fc0d18e..019b6e5429e358bc7dda383aa2df8cd462c39faa 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -5,6 +5,7 @@ use crate::{ MultiBufferSnapshot, }; use anyhow::Context; +use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; use language::{Buffer, BufferSnapshot}; use log::error; @@ -31,6 +32,7 @@ struct CacheSnapshot { struct CachedExcerptHints { version: usize, + buffer_version: Global, hints: Vec<(InlayId, InlayHint)>, } @@ -41,7 +43,14 @@ struct ExcerptQuery { excerpt_range_start: language::Anchor, excerpt_range_end: language::Anchor, cache_version: usize, - invalidate_cache: bool, + invalidate: InvalidationStrategy, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidationStrategy { + All, + OnConflict, + None, } #[derive(Debug, Default)] @@ -116,10 +125,14 @@ impl InlayHintCache { pub fn spawn_hints_update( &mut self, mut excerpts_to_query: HashMap>, - invalidate_cache: bool, + invalidate: InvalidationStrategy, cx: &mut ViewContext, ) { let update_tasks = &mut self.update_tasks; + let invalidate_cache = matches!( + invalidate, + InvalidationStrategy::All | InvalidationStrategy::OnConflict + ); if invalidate_cache { update_tasks .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); @@ -179,7 +192,7 @@ impl InlayHintCache { excerpt_range_start: excerpt_range.start, excerpt_range_end: excerpt_range.end, cache_version, - invalidate_cache, + invalidate, }; let cached_excxerpt_hints = editor .inlay_hint_cache @@ -187,6 +200,20 @@ impl InlayHintCache { .hints .get(&excerpt_id) .cloned(); + + if let Some(cached_excerpt_hints) = &cached_excxerpt_hints { + let new_task_buffer_version = buffer_snapshot.version(); + let cached_buffer_version = &cached_excerpt_hints.buffer_version; + if cached_buffer_version.changed_since(new_task_buffer_version) { + return; + } + if !new_task_buffer_version.changed_since(&cached_buffer_version) + && !matches!(invalidate, InvalidationStrategy::All) + { + return; + } + } + editor.inlay_hint_cache.update_tasks.insert( excerpt_id, new_update_task( @@ -252,6 +279,7 @@ fn new_update_task( .or_insert_with(|| { Arc::new(CachedExcerptHints { version: new_update.cache_version, + buffer_version: buffer_snapshot.version().clone(), hints: Vec::new(), }) }); @@ -267,7 +295,8 @@ fn new_update_task( cached_excerpt_hints.hints.retain(|(hint_id, _)| { !new_update.remove_from_cache.contains(hint_id) }); - + cached_excerpt_hints.buffer_version = + buffer_snapshot.version().clone(); editor.inlay_hint_cache.snapshot.version += 1; let mut splice = InlaySplice { @@ -457,7 +486,10 @@ fn new_excerpt_hints_update_result( let mut remove_from_visible = Vec::new(); let mut remove_from_cache = HashSet::default(); - if query.invalidate_cache { + if matches!( + query.invalidate, + InvalidationStrategy::All | InvalidationStrategy::OnConflict + ) { remove_from_visible.extend( visible_hints .iter() From a68e68a0d9e93ee7515c829f13d3f3dad0875da6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 23 Jun 2023 13:11:09 +0300 Subject: [PATCH 138/169] Properly filter out new hints outside of excerpts' visible ranges --- crates/editor/src/editor.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 29 +++++++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 288ee516d106de78a1d662b46dc00c8e7173ba32..fa23c1be782d4ca8dc7d9fd32a575458d39a0ab9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -32,7 +32,7 @@ use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; -pub use editor_settings::EditorSettings; +pub use editor_settings::{EditorSettings, InlayHints, InlayHintsContent}; pub use element::{ Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, }; diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 019b6e5429e358bc7dda383aa2df8cd462c39faa..b768ffd8575b2a0a4e1ee4c4b6d1ad1ae724b7cb 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -45,6 +45,17 @@ struct ExcerptQuery { cache_version: usize, invalidate: InvalidationStrategy, } +impl ExcerptQuery { + fn contains_position(&self, position: text::Anchor, buffer_snapshot: &BufferSnapshot) -> bool { + self.excerpt_range_start + .cmp(&position, buffer_snapshot) + .is_le() + && self + .excerpt_range_end + .cmp(&position, buffer_snapshot) + .is_ge() + } +} #[derive(Debug, Clone, Copy)] pub enum InvalidationStrategy { @@ -284,7 +295,7 @@ fn new_update_task( }) }); let cached_excerpt_hints = Arc::get_mut(cached_excerpt_hints) - .expect("Cached excerot hints were dropped with the task"); + .expect("Cached excerpt hints were dropped with the task"); match new_update.cache_version.cmp(&cached_excerpt_hints.version) { cmp::Ordering::Less => return, @@ -460,6 +471,9 @@ fn new_excerpt_hints_update_result( let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { + if !query.contains_position(new_hint.position, buffer_snapshot) { + continue; + } let missing_from_cache = match &cached_excerpt_hints { Some(cached_excerpt_hints) => { match cached_excerpt_hints.hints.binary_search_by(|probe| { @@ -494,18 +508,7 @@ fn new_excerpt_hints_update_result( visible_hints .iter() .filter(|hint| hint.position.excerpt_id == query.excerpt_id) - .filter(|hint| { - query - .excerpt_range_start - .cmp(&hint.position.text_anchor, buffer_snapshot) - .is_le() - }) - .filter(|hint| { - query - .excerpt_range_end - .cmp(&hint.position.text_anchor, buffer_snapshot) - .is_ge() - }) + .filter(|hint| query.contains_position(hint.position.text_anchor, buffer_snapshot)) .map(|inlay_hint| inlay_hint.id) .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); From 890b164278b8931c8ed6e6d0bb1da5cc8be3c302 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 23 Jun 2023 21:01:07 +0300 Subject: [PATCH 139/169] Forward inlay hint refresh requests to clients, test coop inlay hints --- crates/collab/src/rpc.rs | 15 +- crates/collab/src/tests/integration_tests.rs | 313 ++++++++++++++++++- crates/editor/src/editor.rs | 4 + crates/editor/src/inlay_hint_cache.rs | 18 +- crates/project/src/project.rs | 21 +- crates/rpc/proto/zed.proto | 5 + crates/rpc/src/proto.rs | 3 + 7 files changed, 366 insertions(+), 13 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 583c708e0a9eae99532e6d8d9604b447b5b7105d..14d785307d317ee03136f8cfbc728c73237d0451 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -201,6 +201,7 @@ impl Server { .add_message_handler(update_language_server) .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) + .add_message_handler(refresh_inlay_hints) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) @@ -1575,6 +1576,10 @@ async fn update_worktree_settings( Ok(()) } +async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> { + broadcast_project_message(request.project_id, request, session).await +} + async fn start_language_server( request: proto::StartLanguageServer, session: Session, @@ -1751,7 +1756,15 @@ async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Re } async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); + broadcast_project_message(request.project_id, request, session).await +} + +async fn broadcast_project_message( + project_id: u64, + request: T, + session: Session, +) -> Result<()> { + let project_id = ProjectId::from_proto(project_id); let project_connection_ids = session .db() .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 3ddef9410427a199a029e8d16b7d0240527527c4..d9644856cd05901b34fa74a6a549f414ff292876 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7,8 +7,8 @@ use client::{User, RECEIVE_TIMEOUT}; use collections::HashSet; use editor::{ test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, - ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, - Undo, + ConfirmRename, Editor, EditorSettings, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, + ToggleCodeActions, Undo, }; use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; @@ -24,7 +24,9 @@ use language::{ }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; +use project::{ + search::SearchQuery, DiagnosticSummary, HoverBlockKind, InlayHintKind, Project, ProjectPath, +}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -34,7 +36,7 @@ use std::{ path::{Path, PathBuf}, rc::Rc, sync::{ - atomic::{AtomicBool, Ordering::SeqCst}, + atomic::{AtomicBool, AtomicU32, Ordering::SeqCst}, Arc, }, }; @@ -6404,6 +6406,7 @@ async fn test_basic_following( let client_b = server.create_client(cx_b, "user_b").await; let client_c = server.create_client(cx_c, "user_c").await; let client_d = server.create_client(cx_d, "user_d").await; + server .create_room(&mut [ (&client_a, cx_a), @@ -7800,6 +7803,308 @@ async fn test_on_input_format_from_guest_to_host( }); } +#[gpui::test] +async fn test_mutual_editor_inlay_hint_cache_update( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + 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); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.inlay_hints = Some(editor::InlayHintsContent { + enabled: Some(true), + show_type_hints: Some(true), + show_parameter_hints: Some(false), + show_other_hints: Some(true), + }) + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.inlay_hints = Some(editor::InlayHintsContent { + enabled: Some(true), + show_type_hints: Some(true), + show_parameter_hints: Some(false), + show_other_hints: Some(true), + }) + }); + }); + }); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry.add(Arc::clone(&language)); + client_b.language_registry.add(language); + + client_a + .fs + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + let (window_a, _) = cx_a.add_window(|_| EmptyView); + let editor_a = cx_a.add_view(window_a, |cx| { + Editor::for_buffer(buffer_a, Some(project_a.clone()), cx) + }); + editor_a.update(cx_a, |_, cx| cx.focus(&editor_a)); + cx_a.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert!( + inlay_cache.hints.is_empty(), + "No inlays should be in the new cache" + ); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, 0, + "New cache should have no version updates" + ); + }); + + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + let (window_b, _) = cx_b.add_window(|_| EmptyView); + let editor_b = cx_b.add_view(window_b, |cx| { + Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) + }); + editor_b.update(cx_b, |_, cx| cx.focus(&editor_b)); + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert!( + inlay_cache.hints.is_empty(), + "No inlays should be in the new cache" + ); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, 0, + "New cache should have no version updates" + ); + }); + + cx_a.foreground().start_waiting(); + let mut edits_made = 0; + let fake_language_server = fake_language_servers.next().await.unwrap(); + editor_b.update(cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); + editor.handle_input(":", cx); + cx.focus(&editor_b); + edits_made += 1; + }); + let next_call_id = Arc::new(AtomicU32::new(0)); + fake_language_server + .handle_request::(move |params, _| { + let task_next_call_id = Arc::clone(&next_call_id); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst); + let mut new_hints = Vec::with_capacity(current_call_id as usize); + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, current_call_id), + label: lsp::InlayHintLabel::String(current_call_id.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if current_call_id == 0 { + break; + } + current_call_id -= 1; + } + Ok(Some(new_hints)) + } + }) + .next() + .await + .unwrap(); + cx_a.foreground().finish_waiting(); + cx_a.foreground().run_until_parked(); + + fn extract_hint_labels(editor: &Editor) -> Vec<&str> { + editor + .inlay_hint_cache() + .snapshot() + .hints + .iter() + .map(|(_, excerpt_hints)| { + excerpt_hints + .hints + .iter() + .map(|(_, inlay)| match &inlay.label { + project::InlayHintLabel::String(s) => s.as_str(), + _ => unreachable!(), + }) + }) + .flatten() + .collect::>() + } + + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0"], + extract_hint_labels(editor), + "Host should get hints from the 1st edit and 1st LSP query" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); + assert_eq!( + inlay_cache.version, edits_made, + "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + ); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0", "1"], + extract_hint_labels(editor), + "Guest should get hints the 1st edit and 2nd LSP query" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); + assert_eq!( + inlay_cache.version, edits_made, + "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + ); + }); + + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("a change to increment both buffers' versions", cx); + cx.focus(&editor_a); + edits_made += 1; + }); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0", "1", "2"], + extract_hint_labels(editor), + "Host should get hints from 3rd edit, 5th LSP query: \ +4th query was made by guest (but not applied) due to cache invalidation logic" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); + assert_eq!( + inlay_cache.version, edits_made, + "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + ); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0", "1", "2", "3"], + extract_hint_labels(editor), + "Guest should get hints from 3rd edit, 6th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest should have a version increment" + ); + }); + + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + edits_made += 1; + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0", "1", "2", "3", "4"], + extract_hint_labels(editor), + "Host should react to /refresh LSP request and get new hints from 7th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Host should accepted all edits and bump its cache version every time" + ); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0", "1", "2", "3", "4", "5"], + extract_hint_labels(editor), + "Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache().snapshot(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, + edits_made, + "Gues should accepted all edits and bump its cache version every time" + ); + }); +} + #[derive(Debug, Eq, PartialEq)] struct RoomParticipants { remote: Vec, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fa23c1be782d4ca8dc7d9fd32a575458d39a0ab9..753adc16d69f7fff1bea285d8fcbe6747392e1ae 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7596,6 +7596,10 @@ impl Editor { pub fn next_inlay_id(&mut self) -> InlayId { InlayId(post_inc(&mut self.next_inlay_id)) } + + pub fn inlay_hint_cache(&self) -> &InlayHintCache { + &self.inlay_hint_cache + } } fn consume_contiguous_rows( diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b768ffd8575b2a0a4e1ee4c4b6d1ad1ae724b7cb..d94a392d53f45a4303aa44f591e54bac7372489f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -24,16 +24,18 @@ struct InlayHintUpdateTask { _task: Task<()>, } -struct CacheSnapshot { - hints: HashMap>, - allowed_hint_kinds: HashSet>, - version: usize, +#[derive(Debug)] +pub struct CacheSnapshot { + pub hints: HashMap>, + pub allowed_hint_kinds: HashSet>, + pub version: usize, } -struct CachedExcerptHints { +#[derive(Debug)] +pub struct CachedExcerptHints { version: usize, buffer_version: Global, - hints: Vec<(InlayId, InlayHint)>, + pub hints: Vec<(InlayId, InlayHint)>, } #[derive(Debug, Clone, Copy)] @@ -91,6 +93,10 @@ impl InlayHintCache { } } + pub fn snapshot(&self) -> &CacheSnapshot { + &self.snapshot + } + pub fn update_settings( &mut self, multi_buffer: &ModelHandle, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a248086494ec0910561ae9123a8cfe0f3fb423f8..a24581a610377f110faac916f7ba8346f51027e0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -563,6 +563,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_refresh_inlay_hints); client.add_model_request_handler(Self::handle_reload_buffers); client.add_model_request_handler(Self::handle_synchronize_buffers); client.add_model_request_handler(Self::handle_format_buffers); @@ -2855,9 +2856,13 @@ impl Project { let this = this .upgrade(&cx) .ok_or_else(|| anyhow!("project dropped"))?; - this.update(&mut cx, |_, cx| { + this.update(&mut cx, |project, cx| { cx.emit(Event::RefreshInlays); - }); + project.remote_id().map(|project_id| { + project.client.send(proto::RefreshInlayHints { project_id }) + }) + }) + .transpose()?; Ok(()) } }) @@ -6776,6 +6781,18 @@ impl Project { })) } + async fn handle_refresh_inlay_hints( + this: ModelHandle, + _: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + this.update(&mut cx, |_, cx| { + cx.emit(Event::RefreshInlays); + }); + Ok(proto::Ack {}) + } + async fn handle_lsp_command( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 838a0123c071089ad499408e433688c25a521f63..0950098738f09d08b13ef52efa2e402ed0e32de8 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -139,6 +139,7 @@ message Envelope { InlayHints inlay_hints = 114; InlayHintsResponse inlay_hints_response = 115; + RefreshInlayHints refresh_inlay_hints = 116; } } @@ -761,6 +762,10 @@ message InlayHintLabelPartTooltip { } } +message RefreshInlayHints { + uint64 project_id = 1; +} + message MarkupContent { string kind = 1; string value = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index d917ff10cf493e0775fc4b0c1a96e2e719c7b502..605b05a56285a2f2c18c001136898adeb1e75d97 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -200,6 +200,7 @@ messages!( (OnTypeFormattingResponse, Background), (InlayHints, Background), (InlayHintsResponse, Background), + (RefreshInlayHints, Foreground), (Ping, Foreground), (PrepareRename, Background), (PrepareRenameResponse, Background), @@ -289,6 +290,7 @@ request_messages!( (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), (InlayHints, InlayHintsResponse), + (RefreshInlayHints, Ack), (ReloadBuffers, ReloadBuffersResponse), (RequestContact, Ack), (RemoveContact, Ack), @@ -336,6 +338,7 @@ entity_messages!( PerformRename, OnTypeFormatting, InlayHints, + RefreshInlayHints, PrepareRename, ReloadBuffers, RemoveProjectCollaborator, From 83b3a914bceb1cb1bcd112886a9acbea84a71354 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 24 Jun 2023 01:08:20 +0300 Subject: [PATCH 140/169] Support better inlay cache parallelization --- crates/collab/src/tests/integration_tests.rs | 72 ++-- crates/editor/src/inlay_hint_cache.rs | 329 +++++++++---------- 2 files changed, 198 insertions(+), 203 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d9644856cd05901b34fa74a6a549f414ff292876..35b6f2c100e7a30d8bb5531858ce1a06ad815e1a 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7891,11 +7891,11 @@ async fn test_mutual_editor_inlay_hint_cache_update( editor_a.update(cx_a, |_, cx| cx.focus(&editor_a)); cx_a.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { - let inlay_cache = editor.inlay_hint_cache().snapshot(); assert!( - inlay_cache.hints.is_empty(), + extract_hint_labels(editor).is_empty(), "No inlays should be in the new cache" ); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Cache should use editor settings to get the allowed hint kinds" @@ -7918,11 +7918,11 @@ async fn test_mutual_editor_inlay_hint_cache_update( editor_b.update(cx_b, |_, cx| cx.focus(&editor_b)); cx_b.foreground().run_until_parked(); editor_b.update(cx_b, |editor, _| { - let inlay_cache = editor.inlay_hint_cache().snapshot(); assert!( - inlay_cache.hints.is_empty(), + extract_hint_labels(editor).is_empty(), "No inlays should be in the new cache" ); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Cache should use editor settings to get the allowed hint kinds" @@ -7978,32 +7978,27 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.foreground().finish_waiting(); cx_a.foreground().run_until_parked(); - fn extract_hint_labels(editor: &Editor) -> Vec<&str> { - editor - .inlay_hint_cache() - .snapshot() - .hints - .iter() - .map(|(_, excerpt_hints)| { - excerpt_hints - .hints - .iter() - .map(|(_, inlay)| match &inlay.label { - project::InlayHintLabel::String(s) => s.as_str(), - _ => unreachable!(), - }) - }) - .flatten() - .collect::>() + fn extract_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + labels } editor_a.update(cx_a, |editor, _| { assert_eq!( - vec!["0"], + vec!["0".to_string()], extract_hint_labels(editor), "Host should get hints from the 1st edit and 1st LSP query" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); assert_eq!( inlay_cache.version, edits_made, @@ -8012,11 +8007,11 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); editor_b.update(cx_b, |editor, _| { assert_eq!( - vec!["0", "1"], + vec!["0".to_string(), "1".to_string()], extract_hint_labels(editor), "Guest should get hints the 1st edit and 2nd LSP query" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); assert_eq!( inlay_cache.version, edits_made, @@ -8034,12 +8029,12 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_b.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert_eq!( - vec!["0", "1", "2"], + vec!["0".to_string(), "1".to_string(), "2".to_string()], extract_hint_labels(editor), "Host should get hints from 3rd edit, 5th LSP query: \ 4th query was made by guest (but not applied) due to cache invalidation logic" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); assert_eq!( inlay_cache.version, edits_made, @@ -8048,11 +8043,16 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); editor_b.update(cx_b, |editor, _| { assert_eq!( - vec!["0", "1", "2", "3"], + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string() + ], extract_hint_labels(editor), "Guest should get hints from 3rd edit, 6th LSP query" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test" @@ -8072,11 +8072,17 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_b.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert_eq!( - vec!["0", "1", "2", "3", "4"], + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string() + ], extract_hint_labels(editor), "Host should react to /refresh LSP request and get new hints from 7th LSP query" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test" @@ -8088,11 +8094,11 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); editor_b.update(cx_b, |editor, _| { assert_eq!( - vec!["0", "1", "2", "3", "4", "5"], + vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()], extract_hint_labels(editor), "Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query" ); - let inlay_cache = editor.inlay_hint_cache().snapshot(); + let inlay_cache = editor.inlay_hint_cache(); assert_eq!( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test" diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d94a392d53f45a4303aa44f591e54bac7372489f..aa10807b0427bebbbab03d1d554003a886d23e4f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -9,13 +9,16 @@ use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; use language::{Buffer, BufferSnapshot}; use log::error; +use parking_lot::RwLock; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; use util::post_inc; pub struct InlayHintCache { - snapshot: CacheSnapshot, + pub hints: HashMap>>, + pub allowed_hint_kinds: HashSet>, + pub version: usize, update_tasks: HashMap, } @@ -24,13 +27,6 @@ struct InlayHintUpdateTask { _task: Task<()>, } -#[derive(Debug)] -pub struct CacheSnapshot { - pub hints: HashMap>, - pub allowed_hint_kinds: HashSet>, - pub version: usize, -} - #[derive(Debug)] pub struct CachedExcerptHints { version: usize, @@ -84,19 +80,13 @@ struct ExcerptHintsUpdate { impl InlayHintCache { pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { Self { - snapshot: CacheSnapshot { - allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), - hints: HashMap::default(), - version: 0, - }, + allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), + hints: HashMap::default(), update_tasks: HashMap::default(), + version: 0, } } - pub fn snapshot(&self) -> &CacheSnapshot { - &self.snapshot - } - pub fn update_settings( &mut self, multi_buffer: &ModelHandle, @@ -106,37 +96,33 @@ impl InlayHintCache { ) -> Option { let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); if !inlay_hint_settings.enabled { - if self.snapshot.hints.is_empty() { - self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; + if self.hints.is_empty() { + self.allowed_hint_kinds = new_allowed_hint_kinds; + None } else { self.clear(); - self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; - return Some(InlaySplice { + self.allowed_hint_kinds = new_allowed_hint_kinds; + Some(InlaySplice { to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), to_insert: Vec::new(), - }); + }) } - - return None; - } - - if new_allowed_hint_kinds == self.snapshot.allowed_hint_kinds { - return None; - } - - let new_splice = new_allowed_hint_kinds_splice( - &self.snapshot, - multi_buffer, - &visible_hints, - &new_allowed_hint_kinds, - cx, - ); - if new_splice.is_some() { - self.snapshot.version += 1; - self.update_tasks.clear(); - self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; + } else if new_allowed_hint_kinds == self.allowed_hint_kinds { + None + } else { + let new_splice = self.new_allowed_hint_kinds_splice( + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); + if new_splice.is_some() { + self.version += 1; + self.update_tasks.clear(); + self.allowed_hint_kinds = new_allowed_hint_kinds; + } + new_splice } - new_splice } pub fn spawn_hints_update( @@ -154,9 +140,10 @@ impl InlayHintCache { update_tasks .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); } + let cache_version = self.version; excerpts_to_query.retain(|visible_excerpt_id, _| { match update_tasks.entry(*visible_excerpt_id) { - hash_map::Entry::Occupied(o) => match o.get().version.cmp(&self.snapshot.version) { + hash_map::Entry::Occupied(o) => match o.get().version.cmp(&cache_version) { cmp::Ordering::Less => true, cmp::Ordering::Equal => invalidate_cache, cmp::Ordering::Greater => false, @@ -169,7 +156,6 @@ impl InlayHintCache { update_tasks .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); } - let cache_version = self.snapshot.version; excerpts_to_query.retain(|visible_excerpt_id, _| { match update_tasks.entry(*visible_excerpt_id) { hash_map::Entry::Occupied(o) => match o.get().version.cmp(&cache_version) { @@ -211,15 +197,12 @@ impl InlayHintCache { cache_version, invalidate, }; - let cached_excxerpt_hints = editor - .inlay_hint_cache - .snapshot - .hints - .get(&excerpt_id) - .cloned(); + let cached_excxerpt_hints = + editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); if let Some(cached_excerpt_hints) = &cached_excxerpt_hints { let new_task_buffer_version = buffer_snapshot.version(); + let cached_excerpt_hints = cached_excerpt_hints.read(); let cached_buffer_version = &cached_excerpt_hints.buffer_version; if cached_buffer_version.changed_since(new_task_buffer_version) { return; @@ -250,11 +233,113 @@ impl InlayHintCache { .detach(); } + fn new_allowed_hint_kinds_splice( + &self, + multi_buffer: &ModelHandle, + visible_hints: &[Inlay], + new_kinds: &HashSet>, + cx: &mut ViewContext, + ) -> Option { + let old_kinds = &self.allowed_hint_kinds; + if new_kinds == old_kinds { + return None; + } + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let mut shown_hints_to_remove = visible_hints.iter().fold( + HashMap::>::default(), + |mut current_hints, inlay| { + current_hints + .entry(inlay.position.excerpt_id) + .or_default() + .push((inlay.position, inlay.id)); + current_hints + }, + ); + + let multi_buffer = multi_buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + + for (excerpt_id, excerpt_cached_hints) in &self.hints { + let shown_excerpt_hints_to_remove = + shown_hints_to_remove.entry(*excerpt_id).or_default(); + let excerpt_cached_hints = excerpt_cached_hints.read(); + let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + let Some(buffer) = shown_anchor + .buffer_id + .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false }; + let buffer_snapshot = buffer.read(cx).snapshot(); + loop { + match excerpt_cache.peek() { + Some((cached_hint_id, cached_hint)) => { + if cached_hint_id == shown_hint_id { + excerpt_cache.next(); + return !new_kinds.contains(&cached_hint.kind); + } + + match cached_hint + .position + .cmp(&shown_anchor.text_anchor, &buffer_snapshot) + { + cmp::Ordering::Less | cmp::Ordering::Equal => { + if !old_kinds.contains(&cached_hint.kind) + && new_kinds.contains(&cached_hint.kind) + { + to_insert.push(( + multi_buffer_snapshot.anchor_in_excerpt( + *excerpt_id, + cached_hint.position, + ), + *cached_hint_id, + cached_hint.clone(), + )); + } + excerpt_cache.next(); + } + cmp::Ordering::Greater => return true, + } + } + None => return true, + } + } + }); + + for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache { + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + to_insert.push(( + multi_buffer_snapshot + .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position), + *cached_hint_id, + maybe_missed_cached_hint.clone(), + )); + } + } + } + + to_remove.extend( + shown_hints_to_remove + .into_values() + .flatten() + .map(|(_, hint_id)| hint_id), + ); + if to_remove.is_empty() && to_insert.is_empty() { + None + } else { + Some(InlaySplice { + to_remove, + to_insert, + }) + } + } + fn clear(&mut self) { - self.snapshot.version += 1; + self.version += 1; self.update_tasks.clear(); - self.snapshot.hints.clear(); - self.snapshot.allowed_hint_kinds.clear(); + self.hints.clear(); + self.allowed_hint_kinds.clear(); } } @@ -263,7 +348,7 @@ fn new_update_task( multi_buffer_snapshot: MultiBufferSnapshot, buffer_snapshot: BufferSnapshot, visible_hints: Arc>, - cached_excerpt_hints: Option>, + cached_excerpt_hints: Option>>, cx: &mut ViewContext<'_, '_, Editor>, ) -> InlayHintUpdateTask { let hints_fetch_task = hints_fetch_task(query, cx); @@ -290,19 +375,16 @@ fn new_update_task( .update(&mut cx, |editor, cx| { let cached_excerpt_hints = editor .inlay_hint_cache - .snapshot .hints .entry(new_update.excerpt_id) .or_insert_with(|| { - Arc::new(CachedExcerptHints { + Arc::new(RwLock::new(CachedExcerptHints { version: new_update.cache_version, buffer_version: buffer_snapshot.version().clone(), hints: Vec::new(), - }) + })) }); - let cached_excerpt_hints = Arc::get_mut(cached_excerpt_hints) - .expect("Cached excerpt hints were dropped with the task"); - + let mut cached_excerpt_hints = cached_excerpt_hints.write(); match new_update.cache_version.cmp(&cached_excerpt_hints.version) { cmp::Ordering::Less => return, cmp::Ordering::Greater | cmp::Ordering::Equal => { @@ -314,7 +396,7 @@ fn new_update_task( }); cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); - editor.inlay_hint_cache.snapshot.version += 1; + editor.inlay_hint_cache.version += 1; let mut splice = InlaySplice { to_remove: new_update.remove_from_visible, @@ -327,7 +409,6 @@ fn new_update_task( let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); if editor .inlay_hint_cache - .snapshot .allowed_hint_kinds .contains(&new_hint.kind) { @@ -346,6 +427,7 @@ fn new_update_task( .sort_by(|(_, hint_a), (_, hint_b)| { hint_a.position.cmp(&hint_b.position, &buffer_snapshot) }); + drop(cached_excerpt_hints); let InlaySplice { to_remove, @@ -368,109 +450,11 @@ fn new_update_task( } } -fn new_allowed_hint_kinds_splice( - cache: &CacheSnapshot, - multi_buffer: &ModelHandle, - visible_hints: &[Inlay], - new_kinds: &HashSet>, - cx: &mut ViewContext, -) -> Option { - let old_kinds = &cache.allowed_hint_kinds; - if new_kinds == old_kinds { - return None; - } - - let mut to_remove = Vec::new(); - let mut to_insert = Vec::new(); - let mut shown_hints_to_remove = visible_hints.iter().fold( - HashMap::>::default(), - |mut current_hints, inlay| { - current_hints - .entry(inlay.position.excerpt_id) - .or_default() - .push((inlay.position, inlay.id)); - current_hints - }, - ); - - let multi_buffer = multi_buffer.read(cx); - let multi_buffer_snapshot = multi_buffer.snapshot(cx); - - for (excerpt_id, excerpt_cached_hints) in &cache.hints { - let shown_excerpt_hints_to_remove = shown_hints_to_remove.entry(*excerpt_id).or_default(); - let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable(); - shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { - let Some(buffer) = shown_anchor - .buffer_id - .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false }; - let buffer_snapshot = buffer.read(cx).snapshot(); - loop { - match excerpt_cache.peek() { - Some((cached_hint_id, cached_hint)) => { - if cached_hint_id == shown_hint_id { - excerpt_cache.next(); - return !new_kinds.contains(&cached_hint.kind); - } - - match cached_hint - .position - .cmp(&shown_anchor.text_anchor, &buffer_snapshot) - { - cmp::Ordering::Less | cmp::Ordering::Equal => { - if !old_kinds.contains(&cached_hint.kind) - && new_kinds.contains(&cached_hint.kind) - { - to_insert.push(( - multi_buffer_snapshot - .anchor_in_excerpt(*excerpt_id, cached_hint.position), - *cached_hint_id, - cached_hint.clone(), - )); - } - excerpt_cache.next(); - } - cmp::Ordering::Greater => return true, - } - } - None => return true, - } - } - }); - - for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache { - let cached_hint_kind = maybe_missed_cached_hint.kind; - if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { - to_insert.push(( - multi_buffer_snapshot - .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position), - *cached_hint_id, - maybe_missed_cached_hint.clone(), - )); - } - } - } - - to_remove.extend( - shown_hints_to_remove - .into_values() - .flatten() - .map(|(_, hint_id)| hint_id), - ); - if to_remove.is_empty() && to_insert.is_empty() { - None - } else { - Some(InlaySplice { - to_remove, - to_insert, - }) - } -} - fn new_excerpt_hints_update_result( query: ExcerptQuery, new_excerpt_hints: Vec, buffer_snapshot: &BufferSnapshot, - cached_excerpt_hints: Option>, + cached_excerpt_hints: Option>>, visible_hints: &[Inlay], ) -> Option { let mut add_to_cache: Vec = Vec::new(); @@ -482,6 +466,7 @@ fn new_excerpt_hints_update_result( } let missing_from_cache = match &cached_excerpt_hints { Some(cached_excerpt_hints) => { + let cached_excerpt_hints = cached_excerpt_hints.read(); match cached_excerpt_hints.hints.binary_search_by(|probe| { probe.1.position.cmp(&new_hint.position, buffer_snapshot) }) { @@ -518,15 +503,19 @@ fn new_excerpt_hints_update_result( .map(|inlay_hint| inlay_hint.id) .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); - remove_from_cache.extend( - cached_excerpt_hints - .iter() - .flat_map(|excerpt_hints| excerpt_hints.hints.iter()) - .filter(|(cached_inlay_id, _)| { - !excerpt_hints_to_persist.contains_key(cached_inlay_id) - }) - .map(|(cached_inlay_id, _)| *cached_inlay_id), - ); + + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let cached_excerpt_hints = cached_excerpt_hints.read(); + remove_from_cache.extend( + cached_excerpt_hints + .hints + .iter() + .filter(|(cached_inlay_id, _)| { + !excerpt_hints_to_persist.contains_key(cached_inlay_id) + }) + .map(|(cached_inlay_id, _)| *cached_inlay_id), + ); + } } if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() { From 2c7900e11b2c3ec816f4113b7e9e95790bc3e3f5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 24 Jun 2023 01:52:48 +0300 Subject: [PATCH 141/169] Use excerpt visible range in query filtering --- crates/collab/src/tests/integration_tests.rs | 10 +- crates/editor/src/editor.rs | 6 +- crates/editor/src/inlay_hint_cache.rs | 145 ++++++++++++------- 3 files changed, 107 insertions(+), 54 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 35b6f2c100e7a30d8bb5531858ce1a06ad815e1a..d6f9846ab5fc7205845fd7b2521144afd362075c 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7888,7 +7888,10 @@ async fn test_mutual_editor_inlay_hint_cache_update( let editor_a = cx_a.add_view(window_a, |cx| { Editor::for_buffer(buffer_a, Some(project_a.clone()), cx) }); - editor_a.update(cx_a, |_, cx| cx.focus(&editor_a)); + editor_a.update(cx_a, |editor, cx| { + editor.set_scroll_position(vec2f(0., 1.), cx); + cx.focus(&editor_a) + }); cx_a.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert!( @@ -7915,7 +7918,10 @@ async fn test_mutual_editor_inlay_hint_cache_update( let editor_b = cx_b.add_view(window_b, |cx| { Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) }); - editor_b.update(cx_b, |_, cx| cx.focus(&editor_b)); + editor_b.update(cx_b, |editor, cx| { + editor.set_scroll_position(vec2f(0., 1.), cx); + cx.focus(&editor_b) + }); cx_b.foreground().run_until_parked(); editor_b.update(cx_b, |editor, _| { assert!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 753adc16d69f7fff1bea285d8fcbe6747392e1ae..dea36559539cf529a217b65b8832149f66b83c27 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2638,7 +2638,10 @@ impl Editor { let excerpts_to_query = self .excerpt_visible_offsets(cx) .into_iter() - .map(|(buffer, _, excerpt_id)| (excerpt_id, buffer)) + .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) + .map(|(buffer, excerpt_visible_range, excerpt_id)| { + (excerpt_id, (buffer, excerpt_visible_range)) + }) .collect::>(); self.inlay_hint_cache .spawn_hints_update(excerpts_to_query, invalidate_cache, cx) @@ -2661,7 +2664,6 @@ impl Editor { Bias::Left, ); let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; - multi_buffer.range_to_buffer_ranges(multi_buffer_visible_range, cx) } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index aa10807b0427bebbbab03d1d554003a886d23e4f..d75ecbe8882931f579e6c789ec18e84a301f477e 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,4 +1,4 @@ -use std::{cmp, sync::Arc}; +use std::{cmp, ops::Range, sync::Arc}; use crate::{ display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, @@ -38,20 +38,43 @@ pub struct CachedExcerptHints { struct ExcerptQuery { buffer_id: u64, excerpt_id: ExcerptId, - excerpt_range_start: language::Anchor, - excerpt_range_end: language::Anchor, + dimensions: ExcerptDimensions, cache_version: usize, invalidate: InvalidationStrategy, } -impl ExcerptQuery { - fn contains_position(&self, position: text::Anchor, buffer_snapshot: &BufferSnapshot) -> bool { - self.excerpt_range_start - .cmp(&position, buffer_snapshot) - .is_le() - && self - .excerpt_range_end + +#[derive(Debug, Clone, Copy)] +struct ExcerptDimensions { + excerpt_range_start: language::Anchor, + excerpt_range_end: language::Anchor, + excerpt_visible_range_start: language::Anchor, + excerpt_visible_range_end: language::Anchor, +} + +impl ExcerptDimensions { + fn contains_position( + &self, + position: language::Anchor, + buffer_snapshot: &BufferSnapshot, + visible_only: bool, + ) -> bool { + if visible_only { + self.excerpt_visible_range_start .cmp(&position, buffer_snapshot) - .is_ge() + .is_le() + && self + .excerpt_visible_range_end + .cmp(&position, buffer_snapshot) + .is_ge() + } else { + self.excerpt_range_start + .cmp(&position, buffer_snapshot) + .is_le() + && self + .excerpt_range_end + .cmp(&position, buffer_snapshot) + .is_ge() + } } } @@ -127,7 +150,7 @@ impl InlayHintCache { pub fn spawn_hints_update( &mut self, - mut excerpts_to_query: HashMap>, + mut excerpts_to_query: HashMap, Range)>, invalidate: InvalidationStrategy, cx: &mut ViewContext, ) { @@ -172,34 +195,12 @@ impl InlayHintCache { .update(&mut cx, |editor, cx| { let visible_hints = Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); - for (excerpt_id, buffer_handle) in excerpts_to_query { - let (multi_buffer_snapshot, excerpt_range) = - editor.buffer.update(cx, |multi_buffer, cx| { - let multi_buffer_snapshot = multi_buffer.snapshot(cx); - ( - multi_buffer_snapshot, - multi_buffer - .excerpts_for_buffer(&buffer_handle, cx) - .into_iter() - .find(|(id, _)| id == &excerpt_id) - .map(|(_, range)| range.context), - ) - }); - - if let Some(excerpt_range) = excerpt_range { + for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query { + if !excerpt_visible_range.is_empty() { let buffer = buffer_handle.read(cx); let buffer_snapshot = buffer.snapshot(); - let query = ExcerptQuery { - buffer_id: buffer.remote_id(), - excerpt_id, - excerpt_range_start: excerpt_range.start, - excerpt_range_end: excerpt_range.end, - cache_version, - invalidate, - }; let cached_excxerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); - if let Some(cached_excerpt_hints) = &cached_excxerpt_hints { let new_task_buffer_version = buffer_snapshot.version(); let cached_excerpt_hints = cached_excerpt_hints.read(); @@ -214,17 +215,50 @@ impl InlayHintCache { } } - editor.inlay_hint_cache.update_tasks.insert( - excerpt_id, - new_update_task( - query, - multi_buffer_snapshot, - buffer_snapshot, - Arc::clone(&visible_hints), - cached_excxerpt_hints, - cx, - ), - ); + let buffer_id = buffer.remote_id(); + let excerpt_range_start = + buffer.anchor_before(excerpt_visible_range.start); + let excerpt_range_end = buffer.anchor_after(excerpt_visible_range.end); + + let (multi_buffer_snapshot, full_excerpt_range) = + editor.buffer.update(cx, |multi_buffer, cx| { + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + ( + multi_buffer_snapshot, + multi_buffer + .excerpts_for_buffer(&buffer_handle, cx) + .into_iter() + .find(|(id, _)| id == &excerpt_id) + .map(|(_, range)| range.context), + ) + }); + + if let Some(full_excerpt_range) = full_excerpt_range { + let query = ExcerptQuery { + buffer_id, + excerpt_id, + dimensions: ExcerptDimensions { + excerpt_range_start, + excerpt_range_end, + excerpt_visible_range_start: full_excerpt_range.start, + excerpt_visible_range_end: full_excerpt_range.end, + }, + cache_version, + invalidate, + }; + + editor.inlay_hint_cache.update_tasks.insert( + excerpt_id, + new_update_task( + query, + multi_buffer_snapshot, + buffer_snapshot, + Arc::clone(&visible_hints), + cached_excxerpt_hints, + cx, + ), + ); + } } } }) @@ -461,7 +495,10 @@ fn new_excerpt_hints_update_result( let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { - if !query.contains_position(new_hint.position, buffer_snapshot) { + if !query + .dimensions + .contains_position(new_hint.position, buffer_snapshot, false) + { continue; } let missing_from_cache = match &cached_excerpt_hints { @@ -499,7 +536,13 @@ fn new_excerpt_hints_update_result( visible_hints .iter() .filter(|hint| hint.position.excerpt_id == query.excerpt_id) - .filter(|hint| query.contains_position(hint.position.text_anchor, buffer_snapshot)) + .filter(|hint| { + query.dimensions.contains_position( + hint.position.text_anchor, + buffer_snapshot, + false, + ) + }) .map(|inlay_hint| inlay_hint.id) .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); @@ -563,7 +606,9 @@ fn hints_fetch_task( Some(project.update(cx, |project, cx| { project.inlay_hints( buffer, - query.excerpt_range_start..query.excerpt_range_end, + // TODO kb split into 3 queries + query.dimensions.excerpt_range_start + ..query.dimensions.excerpt_range_end, cx, ) })) From 4d4544f680f5bf78ca013c7616417082c7380acc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 24 Jun 2023 02:37:03 +0300 Subject: [PATCH 142/169] Split excerpts into mutliple ranges for inlay hint queries --- crates/collab/src/tests/integration_tests.rs | 52 +- crates/editor/src/editor.rs | 6 +- crates/editor/src/inlay_hint_cache.rs | 503 ++++++++++++------- 3 files changed, 349 insertions(+), 212 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d6f9846ab5fc7205845fd7b2521144afd362075c..288bc8e7afaa5c972eba2b9cb6fbf09af6043249 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7817,6 +7817,10 @@ async fn test_mutual_editor_inlay_hint_cache_update( .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + cx_a.update(editor::init); + cx_b.update(editor::init); cx_a.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { @@ -7876,22 +7880,30 @@ async fn test_mutual_editor_inlay_hint_cache_update( ) .await; let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); - let buffer_a = project_a - .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + + let project_b = client_b.build_remote_project(project_id, cx_b).await; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) .await .unwrap(); - let (window_a, _) = cx_a.add_window(|_| EmptyView); - let editor_a = cx_a.add_view(window_a, |cx| { - Editor::for_buffer(buffer_a, Some(project_a.clone()), cx) - }); - editor_a.update(cx_a, |editor, cx| { - editor.set_scroll_position(vec2f(0., 1.), cx); - cx.focus(&editor_a) - }); + + let workspace_a = client_a.build_workspace(&project_a, cx_a); + let editor_a = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); cx_a.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert!( @@ -7908,20 +7920,16 @@ async fn test_mutual_editor_inlay_hint_cache_update( "New cache should have no version updates" ); }); - - let project_b = client_b.build_remote_project(project_id, cx_b).await; - let buffer_b = project_b - .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + let workspace_b = client_b.build_workspace(&project_b, cx_b); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) .await + .unwrap() + .downcast::() .unwrap(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); - let editor_b = cx_b.add_view(window_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) - }); - editor_b.update(cx_b, |editor, cx| { - editor.set_scroll_position(vec2f(0., 1.), cx); - cx.focus(&editor_b) - }); + cx_b.foreground().run_until_parked(); editor_b.update(cx_b, |editor, _| { assert!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dea36559539cf529a217b65b8832149f66b83c27..b5b5d550ce15e785a4b15434b3c49ca3387ee56d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2643,8 +2643,10 @@ impl Editor { (excerpt_id, (buffer, excerpt_visible_range)) }) .collect::>(); - self.inlay_hint_cache - .spawn_hints_update(excerpts_to_query, invalidate_cache, cx) + if !excerpts_to_query.is_empty() { + self.inlay_hint_cache + .refresh_inlay_hints(excerpts_to_query, invalidate_cache, cx) + } } fn excerpt_visible_offsets( diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d75ecbe8882931f579e6c789ec18e84a301f477e..0b5902938392ac7238ff8681fa7170a99cee4730 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -148,7 +148,7 @@ impl InlayHintCache { } } - pub fn spawn_hints_update( + pub fn refresh_inlay_hints( &mut self, mut excerpts_to_query: HashMap, Range)>, invalidate: InvalidationStrategy, @@ -193,74 +193,7 @@ impl InlayHintCache { cx.spawn(|editor, mut cx| async move { editor .update(&mut cx, |editor, cx| { - let visible_hints = - Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); - for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query { - if !excerpt_visible_range.is_empty() { - let buffer = buffer_handle.read(cx); - let buffer_snapshot = buffer.snapshot(); - let cached_excxerpt_hints = - editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); - if let Some(cached_excerpt_hints) = &cached_excxerpt_hints { - let new_task_buffer_version = buffer_snapshot.version(); - let cached_excerpt_hints = cached_excerpt_hints.read(); - let cached_buffer_version = &cached_excerpt_hints.buffer_version; - if cached_buffer_version.changed_since(new_task_buffer_version) { - return; - } - if !new_task_buffer_version.changed_since(&cached_buffer_version) - && !matches!(invalidate, InvalidationStrategy::All) - { - return; - } - } - - let buffer_id = buffer.remote_id(); - let excerpt_range_start = - buffer.anchor_before(excerpt_visible_range.start); - let excerpt_range_end = buffer.anchor_after(excerpt_visible_range.end); - - let (multi_buffer_snapshot, full_excerpt_range) = - editor.buffer.update(cx, |multi_buffer, cx| { - let multi_buffer_snapshot = multi_buffer.snapshot(cx); - ( - multi_buffer_snapshot, - multi_buffer - .excerpts_for_buffer(&buffer_handle, cx) - .into_iter() - .find(|(id, _)| id == &excerpt_id) - .map(|(_, range)| range.context), - ) - }); - - if let Some(full_excerpt_range) = full_excerpt_range { - let query = ExcerptQuery { - buffer_id, - excerpt_id, - dimensions: ExcerptDimensions { - excerpt_range_start, - excerpt_range_end, - excerpt_visible_range_start: full_excerpt_range.start, - excerpt_visible_range_end: full_excerpt_range.end, - }, - cache_version, - invalidate, - }; - - editor.inlay_hint_cache.update_tasks.insert( - excerpt_id, - new_update_task( - query, - multi_buffer_snapshot, - buffer_snapshot, - Arc::clone(&visible_hints), - cached_excxerpt_hints, - cx, - ), - ); - } - } - } + spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx) }) .ok(); }) @@ -377,6 +310,81 @@ impl InlayHintCache { } } +fn spawn_new_update_tasks( + editor: &mut Editor, + excerpts_to_query: HashMap, Range)>, + invalidate: InvalidationStrategy, + cache_version: usize, + cx: &mut ViewContext<'_, '_, Editor>, +) { + let visible_hints = Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); + for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query { + if !excerpt_visible_range.is_empty() { + let buffer = buffer_handle.read(cx); + let buffer_snapshot = buffer.snapshot(); + let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let new_task_buffer_version = buffer_snapshot.version(); + let cached_excerpt_hints = cached_excerpt_hints.read(); + let cached_buffer_version = &cached_excerpt_hints.buffer_version; + if cached_buffer_version.changed_since(new_task_buffer_version) { + return; + } + // TODO kb on refresh (InvalidationStrategy::All), instead spawn a new task afterwards, that would wait before 1st query finishes + if !new_task_buffer_version.changed_since(&cached_buffer_version) + && !matches!(invalidate, InvalidationStrategy::All) + { + return; + } + } + + let buffer_id = buffer.remote_id(); + let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start); + let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end); + + let (multi_buffer_snapshot, full_excerpt_range) = + editor.buffer.update(cx, |multi_buffer, cx| { + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + ( + multi_buffer_snapshot, + multi_buffer + .excerpts_for_buffer(&buffer_handle, cx) + .into_iter() + .find(|(id, _)| id == &excerpt_id) + .map(|(_, range)| range.context), + ) + }); + + if let Some(full_excerpt_range) = full_excerpt_range { + let query = ExcerptQuery { + buffer_id, + excerpt_id, + dimensions: ExcerptDimensions { + excerpt_range_start: full_excerpt_range.start, + excerpt_range_end: full_excerpt_range.end, + excerpt_visible_range_start, + excerpt_visible_range_end, + }, + cache_version, + invalidate, + }; + + editor.inlay_hint_cache.update_tasks.insert( + excerpt_id, + new_update_task( + query, + multi_buffer_snapshot, + buffer_snapshot, + Arc::clone(&visible_hints), + cached_excerpt_hints, + cx, + ), + ); + } + } + } +} + fn new_update_task( query: ExcerptQuery, multi_buffer_snapshot: MultiBufferSnapshot, @@ -385,107 +393,166 @@ fn new_update_task( cached_excerpt_hints: Option>>, cx: &mut ViewContext<'_, '_, Editor>, ) -> InlayHintUpdateTask { - let hints_fetch_task = hints_fetch_task(query, cx); + let hints_fetch_tasks = hints_fetch_tasks(query, &buffer_snapshot, cx); + let _task = cx.spawn(|editor, cx| async move { + let create_update_task = |range, hint_fetch_task| { + fetch_and_update_hints( + editor.clone(), + multi_buffer_snapshot.clone(), + buffer_snapshot.clone(), + Arc::clone(&visible_hints), + cached_excerpt_hints.as_ref().map(Arc::clone), + query, + range, + hint_fetch_task, + cx.clone(), + ) + }; + + let (visible_range, visible_range_hint_fetch_task) = hints_fetch_tasks.visible_range; + let visible_range_has_updates = + match create_update_task(visible_range, visible_range_hint_fetch_task).await { + Ok(updated) => updated, + Err(e) => { + error!("inlay hint visible range update task failed: {e:#}"); + return; + } + }; + + if visible_range_has_updates { + let other_update_results = + futures::future::join_all(hints_fetch_tasks.other_ranges.into_iter().map( + |(fetch_range, hints_fetch_task)| { + create_update_task(fetch_range, hints_fetch_task) + }, + )) + .await; + + for result in other_update_results { + if let Err(e) = result { + error!("inlay hint update task failed: {e:#}"); + return; + } + } + } + }); + InlayHintUpdateTask { version: query.cache_version, - _task: cx.spawn(|editor, mut cx| async move { - match hints_fetch_task.await { - Ok(Some(new_hints)) => { - let task_buffer_snapshot = buffer_snapshot.clone(); - if let Some(new_update) = cx - .background() - .spawn(async move { - new_excerpt_hints_update_result( - query, - new_hints, - &task_buffer_snapshot, - cached_excerpt_hints, - &visible_hints, - ) - }) - .await - { - editor - .update(&mut cx, |editor, cx| { - let cached_excerpt_hints = editor - .inlay_hint_cache - .hints - .entry(new_update.excerpt_id) - .or_insert_with(|| { - Arc::new(RwLock::new(CachedExcerptHints { - version: new_update.cache_version, - buffer_version: buffer_snapshot.version().clone(), - hints: Vec::new(), - })) - }); - let mut cached_excerpt_hints = cached_excerpt_hints.write(); - match new_update.cache_version.cmp(&cached_excerpt_hints.version) { - cmp::Ordering::Less => return, - cmp::Ordering::Greater | cmp::Ordering::Equal => { - cached_excerpt_hints.version = new_update.cache_version; - } - } - cached_excerpt_hints.hints.retain(|(hint_id, _)| { - !new_update.remove_from_cache.contains(hint_id) - }); - cached_excerpt_hints.buffer_version = - buffer_snapshot.version().clone(); - editor.inlay_hint_cache.version += 1; - - let mut splice = InlaySplice { - to_remove: new_update.remove_from_visible, - to_insert: Vec::new(), - }; - - for new_hint in new_update.add_to_cache { - let new_hint_position = multi_buffer_snapshot - .anchor_in_excerpt(query.excerpt_id, new_hint.position); - let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - splice.to_insert.push(( - new_hint_position, - new_inlay_id, - new_hint.clone(), - )); - } + _task, + } +} - cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); - } +async fn fetch_and_update_hints( + editor: gpui::WeakViewHandle, + multi_buffer_snapshot: MultiBufferSnapshot, + buffer_snapshot: BufferSnapshot, + visible_hints: Arc>, + cached_excerpt_hints: Option>>, + query: ExcerptQuery, + fetch_range: Range, + hints_fetch_task: Task>>>, + mut cx: gpui::AsyncAppContext, +) -> anyhow::Result { + let mut update_happened = false; + match hints_fetch_task.await.context("inlay hint fetch task")? { + Some(new_hints) => { + let background_task_buffer_snapshot = buffer_snapshot.clone(); + let backround_fetch_range = fetch_range.clone(); + if let Some(new_update) = cx + .background() + .spawn(async move { + calculate_hint_updates( + query, + backround_fetch_range, + new_hints, + &background_task_buffer_snapshot, + cached_excerpt_hints, + &visible_hints, + ) + }) + .await + { + update_happened = !new_update.add_to_cache.is_empty() + || !new_update.remove_from_cache.is_empty() + || !new_update.remove_from_visible.is_empty(); + editor + .update(&mut cx, |editor, cx| { + let cached_excerpt_hints = editor + .inlay_hint_cache + .hints + .entry(new_update.excerpt_id) + .or_insert_with(|| { + Arc::new(RwLock::new(CachedExcerptHints { + version: new_update.cache_version, + buffer_version: buffer_snapshot.version().clone(), + hints: Vec::new(), + })) + }); + let mut cached_excerpt_hints = cached_excerpt_hints.write(); + match new_update.cache_version.cmp(&cached_excerpt_hints.version) { + cmp::Ordering::Less => return, + cmp::Ordering::Greater | cmp::Ordering::Equal => { + cached_excerpt_hints.version = new_update.cache_version; + } + } + cached_excerpt_hints + .hints + .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id)); + cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); + editor.inlay_hint_cache.version += 1; + + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + to_insert: Vec::new(), + }; + + for new_hint in new_update.add_to_cache { + let new_hint_position = multi_buffer_snapshot + .anchor_in_excerpt(query.excerpt_id, new_hint.position); + let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + splice.to_insert.push(( + new_hint_position, + new_inlay_id, + new_hint.clone(), + )); + } - cached_excerpt_hints - .hints - .sort_by(|(_, hint_a), (_, hint_b)| { - hint_a.position.cmp(&hint_b.position, &buffer_snapshot) - }); - drop(cached_excerpt_hints); - - let InlaySplice { - to_remove, - to_insert, - } = splice; - if !to_remove.is_empty() || !to_insert.is_empty() { - editor.splice_inlay_hints(to_remove, to_insert, cx) - } - }) - .ok(); - } - } - Ok(None) => {} - Err(e) => error!( - "Failed to fecth hints for excerpt {:?} in buffer {} : {}", - query.excerpt_id, query.buffer_id, e - ), + cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); + } + + cached_excerpt_hints + .hints + .sort_by(|(_, hint_a), (_, hint_b)| { + hint_a.position.cmp(&hint_b.position, &buffer_snapshot) + }); + drop(cached_excerpt_hints); + + let InlaySplice { + to_remove, + to_insert, + } = splice; + if !to_remove.is_empty() || !to_insert.is_empty() { + editor.splice_inlay_hints(to_remove, to_insert, cx) + } + }) + .ok(); } - }), + } + None => {} } + + Ok(update_happened) } -fn new_excerpt_hints_update_result( +fn calculate_hint_updates( query: ExcerptQuery, + fetch_range: Range, new_excerpt_hints: Vec, buffer_snapshot: &BufferSnapshot, cached_excerpt_hints: Option>>, @@ -543,6 +610,16 @@ fn new_excerpt_hints_update_result( false, ) }) + .filter(|hint| { + fetch_range + .start + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_le() + && fetch_range + .end + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_ge() + }) .map(|inlay_hint| inlay_hint.id) .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), ); @@ -556,6 +633,16 @@ fn new_excerpt_hints_update_result( .filter(|(cached_inlay_id, _)| { !excerpt_hints_to_persist.contains_key(cached_inlay_id) }) + .filter(|(_, cached_hint)| { + fetch_range + .start + .cmp(&cached_hint.position, buffer_snapshot) + .is_le() + && fetch_range + .end + .cmp(&cached_hint.position, buffer_snapshot) + .is_ge() + }) .map(|(cached_inlay_id, _)| *cached_inlay_id), ); } @@ -590,37 +677,77 @@ fn allowed_hint_types( new_allowed_hint_types } -fn hints_fetch_task( +struct HintFetchTasks { + visible_range: ( + Range, + Task>>>, + ), + other_ranges: Vec<( + Range, + Task>>>, + )>, +} + +fn hints_fetch_tasks( query: ExcerptQuery, + buffer: &BufferSnapshot, cx: &mut ViewContext<'_, '_, Editor>, -) -> Task>>> { - cx.spawn(|editor, mut cx| async move { - let task = editor - .update(&mut cx, |editor, cx| { - editor - .buffer() - .read(cx) - .buffer(query.buffer_id) - .and_then(|buffer| { - let project = editor.project.as_ref()?; - Some(project.update(cx, |project, cx| { - project.inlay_hints( - buffer, - // TODO kb split into 3 queries - query.dimensions.excerpt_range_start - ..query.dimensions.excerpt_range_end, - cx, - ) - })) - }) +) -> HintFetchTasks { + let visible_range = + query.dimensions.excerpt_visible_range_start..query.dimensions.excerpt_visible_range_end; + let mut other_ranges = Vec::new(); + if query + .dimensions + .excerpt_range_start + .cmp(&query.dimensions.excerpt_visible_range_start, buffer) + .is_lt() + { + let mut end = query.dimensions.excerpt_visible_range_start; + end.offset -= 1; + other_ranges.push(query.dimensions.excerpt_range_start..end); + } + if query + .dimensions + .excerpt_range_end + .cmp(&query.dimensions.excerpt_visible_range_end, buffer) + .is_gt() + { + let mut start = query.dimensions.excerpt_visible_range_end; + start.offset += 1; + other_ranges.push(start..query.dimensions.excerpt_range_end); + } + + let mut query_task_for_range = |range_to_query| { + cx.spawn(|editor, mut cx| async move { + let task = editor + .update(&mut cx, |editor, cx| { + editor + .buffer() + .read(cx) + .buffer(query.buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.inlay_hints(buffer, range_to_query, cx) + })) + }) + }) + .ok() + .flatten(); + anyhow::Ok(match task { + Some(task) => Some(task.await.context("inlays for buffer task")?), + None => None, }) - .ok() - .flatten(); - Ok(match task { - Some(task) => Some(task.await.context("inlays for buffer task")?), - None => None, }) - }) + }; + + HintFetchTasks { + visible_range: (visible_range.clone(), query_task_for_range(visible_range)), + other_ranges: other_ranges + .into_iter() + .map(|range| (range.clone(), query_task_for_range(range))) + .collect(), + } } pub fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( From 11fee4ce42fd4a411e02726a766612e8e23af2e1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 24 Jun 2023 23:46:20 +0300 Subject: [PATCH 143/169] Do not eagerly cancel running tasks --- crates/editor/src/editor.rs | 3 +- crates/editor/src/inlay_hint_cache.rs | 158 +++++++++++++++++++------- 2 files changed, 115 insertions(+), 46 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b5b5d550ce15e785a4b15434b3c49ca3387ee56d..4644336bce2b96edb9ed5e5b35d3001e9f1be580 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2632,7 +2632,7 @@ impl Editor { } InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict, - InlayRefreshReason::RefreshRequested => InvalidationStrategy::All, + InlayRefreshReason::RefreshRequested => InvalidationStrategy::Forced, }; let excerpts_to_query = self @@ -2680,7 +2680,6 @@ impl Editor { .into_iter() .map(|(position, id, hint)| { let mut text = hint.text(); - // TODO kb styling instead? if hint.padding_right { text.push(' '); } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 0b5902938392ac7238ff8681fa7170a99cee4730..6de601f9b23049316c2053b3c777a6abcf9ba01c 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -19,11 +19,17 @@ pub struct InlayHintCache { pub hints: HashMap>>, pub allowed_hint_kinds: HashSet>, pub version: usize, - update_tasks: HashMap, + update_tasks: HashMap, } -struct InlayHintUpdateTask { +struct UpdateTask { + current: (InvalidationStrategy, SpawnedTask), + pending_refresh: Option, +} + +struct SpawnedTask { version: usize, + is_running_rx: smol::channel::Receiver<()>, _task: Task<()>, } @@ -51,6 +57,31 @@ struct ExcerptDimensions { excerpt_visible_range_end: language::Anchor, } +impl UpdateTask { + fn new(invalidation_strategy: InvalidationStrategy, spawned_task: SpawnedTask) -> Self { + Self { + current: (invalidation_strategy, spawned_task), + pending_refresh: None, + } + } + + fn is_running(&self) -> bool { + !self.current.1.is_running_rx.is_closed() + || self + .pending_refresh + .as_ref() + .map_or(false, |task| !task.is_running_rx.is_closed()) + } + + fn cache_version(&self) -> usize { + self.current.1.version + } + + fn invalidation_strategy(&self) -> InvalidationStrategy { + self.current.0 + } +} + impl ExcerptDimensions { fn contains_position( &self, @@ -80,7 +111,7 @@ impl ExcerptDimensions { #[derive(Debug, Clone, Copy)] pub enum InvalidationStrategy { - All, + Forced, OnConflict, None, } @@ -157,7 +188,7 @@ impl InlayHintCache { let update_tasks = &mut self.update_tasks; let invalidate_cache = matches!( invalidate, - InvalidationStrategy::All | InvalidationStrategy::OnConflict + InvalidationStrategy::Forced | InvalidationStrategy::OnConflict ); if invalidate_cache { update_tasks @@ -166,22 +197,7 @@ impl InlayHintCache { let cache_version = self.version; excerpts_to_query.retain(|visible_excerpt_id, _| { match update_tasks.entry(*visible_excerpt_id) { - hash_map::Entry::Occupied(o) => match o.get().version.cmp(&cache_version) { - cmp::Ordering::Less => true, - cmp::Ordering::Equal => invalidate_cache, - cmp::Ordering::Greater => false, - }, - hash_map::Entry::Vacant(_) => true, - } - }); - - if invalidate_cache { - update_tasks - .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); - } - excerpts_to_query.retain(|visible_excerpt_id, _| { - match update_tasks.entry(*visible_excerpt_id) { - hash_map::Entry::Occupied(o) => match o.get().version.cmp(&cache_version) { + hash_map::Entry::Occupied(o) => match o.get().cache_version().cmp(&cache_version) { cmp::Ordering::Less => true, cmp::Ordering::Equal => invalidate_cache, cmp::Ordering::Greater => false, @@ -313,8 +329,8 @@ impl InlayHintCache { fn spawn_new_update_tasks( editor: &mut Editor, excerpts_to_query: HashMap, Range)>, - invalidate: InvalidationStrategy, - cache_version: usize, + invalidation_strategy: InvalidationStrategy, + update_cache_version: usize, cx: &mut ViewContext<'_, '_, Editor>, ) { let visible_hints = Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); @@ -323,20 +339,26 @@ fn spawn_new_update_tasks( let buffer = buffer_handle.read(cx); let buffer_snapshot = buffer.snapshot(); let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); - if let Some(cached_excerpt_hints) = &cached_excerpt_hints { - let new_task_buffer_version = buffer_snapshot.version(); - let cached_excerpt_hints = cached_excerpt_hints.read(); - let cached_buffer_version = &cached_excerpt_hints.buffer_version; - if cached_buffer_version.changed_since(new_task_buffer_version) { - return; - } - // TODO kb on refresh (InvalidationStrategy::All), instead spawn a new task afterwards, that would wait before 1st query finishes - if !new_task_buffer_version.changed_since(&cached_buffer_version) - && !matches!(invalidate, InvalidationStrategy::All) - { - return; + let cache_is_empty = match &cached_excerpt_hints { + Some(cached_excerpt_hints) => { + let new_task_buffer_version = buffer_snapshot.version(); + let cached_excerpt_hints = cached_excerpt_hints.read(); + let cached_buffer_version = &cached_excerpt_hints.buffer_version; + if cached_excerpt_hints.version > update_cache_version + || cached_buffer_version.changed_since(new_task_buffer_version) + { + return; + } + if !new_task_buffer_version.changed_since(&cached_buffer_version) + && !matches!(invalidation_strategy, InvalidationStrategy::Forced) + { + return; + } + + cached_excerpt_hints.hints.is_empty() } - } + None => true, + }; let buffer_id = buffer.remote_id(); let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start); @@ -365,21 +387,62 @@ fn spawn_new_update_tasks( excerpt_visible_range_start, excerpt_visible_range_end, }, - cache_version, - invalidate, + cache_version: update_cache_version, + invalidate: invalidation_strategy, }; - editor.inlay_hint_cache.update_tasks.insert( - excerpt_id, + let new_update_task = |previous_task| { new_update_task( query, multi_buffer_snapshot, buffer_snapshot, Arc::clone(&visible_hints), cached_excerpt_hints, + previous_task, cx, - ), - ); + ) + }; + match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { + hash_map::Entry::Occupied(mut o) => { + let update_task = o.get_mut(); + if update_task.is_running() { + match (update_task.invalidation_strategy(), invalidation_strategy) { + (InvalidationStrategy::Forced, InvalidationStrategy::Forced) + | (_, InvalidationStrategy::OnConflict) => { + o.insert(UpdateTask::new( + invalidation_strategy, + new_update_task(None), + )); + } + (InvalidationStrategy::Forced, _) => {} + (_, InvalidationStrategy::Forced) => { + if cache_is_empty { + o.insert(UpdateTask::new( + invalidation_strategy, + new_update_task(None), + )); + } else if update_task.pending_refresh.is_none() { + update_task.pending_refresh = Some(new_update_task(Some( + update_task.current.1.is_running_rx.clone(), + ))); + } + } + _ => {} + } + } else { + o.insert(UpdateTask::new( + invalidation_strategy, + new_update_task(None), + )); + } + } + hash_map::Entry::Vacant(v) => { + v.insert(UpdateTask::new( + invalidation_strategy, + new_update_task(None), + )); + } + } } } } @@ -391,10 +454,16 @@ fn new_update_task( buffer_snapshot: BufferSnapshot, visible_hints: Arc>, cached_excerpt_hints: Option>>, + previous_task: Option>, cx: &mut ViewContext<'_, '_, Editor>, -) -> InlayHintUpdateTask { +) -> SpawnedTask { let hints_fetch_tasks = hints_fetch_tasks(query, &buffer_snapshot, cx); + let (is_running_tx, is_running_rx) = smol::channel::bounded(1); let _task = cx.spawn(|editor, cx| async move { + let _is_running_tx = is_running_tx; + if let Some(previous_task) = previous_task { + previous_task.recv().await.ok(); + } let create_update_task = |range, hint_fetch_task| { fetch_and_update_hints( editor.clone(), @@ -437,9 +506,10 @@ fn new_update_task( } }); - InlayHintUpdateTask { + SpawnedTask { version: query.cache_version, _task, + is_running_rx, } } @@ -597,7 +667,7 @@ fn calculate_hint_updates( let mut remove_from_cache = HashSet::default(); if matches!( query.invalidate, - InvalidationStrategy::All | InvalidationStrategy::OnConflict + InvalidationStrategy::Forced | InvalidationStrategy::OnConflict ) { remove_from_visible.extend( visible_hints From acef5ff19501c8bbc5c92739e3e49497c1e08bc5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 25 Jun 2023 01:32:45 +0300 Subject: [PATCH 144/169] Query hints when editors gets open and visible --- crates/collab/src/tests/integration_tests.rs | 163 +++++++++++-------- crates/editor/src/editor.rs | 1 - crates/editor/src/element.rs | 2 +- crates/editor/src/scroll.rs | 18 +- 4 files changed, 107 insertions(+), 77 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 288bc8e7afaa5c972eba2b9cb6fbf09af6043249..5ff7f09ff88ddb58734c5794b3e719d6784e3629 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7896,6 +7896,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( .unwrap(); let workspace_a = client_a.build_workspace(&project_a, cx_a); + cx_a.foreground().start_waiting(); + let editor_a = workspace_a .update(cx_a, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -7904,58 +7906,8 @@ async fn test_mutual_editor_inlay_hint_cache_update( .unwrap() .downcast::() .unwrap(); - cx_a.foreground().run_until_parked(); - editor_a.update(cx_a, |editor, _| { - assert!( - extract_hint_labels(editor).is_empty(), - "No inlays should be in the new cache" - ); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, 0, - "New cache should have no version updates" - ); - }); - let workspace_b = client_b.build_workspace(&project_b, cx_b); - let editor_b = workspace_b - .update(cx_b, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - cx_b.foreground().run_until_parked(); - editor_b.update(cx_b, |editor, _| { - assert!( - extract_hint_labels(editor).is_empty(), - "No inlays should be in the new cache" - ); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!( - inlay_cache.allowed_hint_kinds, allowed_hint_kinds, - "Cache should use editor settings to get the allowed hint kinds" - ); - assert_eq!( - inlay_cache.version, 0, - "New cache should have no version updates" - ); - }); - - cx_a.foreground().start_waiting(); - let mut edits_made = 0; let fake_language_server = fake_language_servers.next().await.unwrap(); - editor_b.update(cx_b, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); - editor.handle_input(":", cx); - cx.focus(&editor_b); - edits_made += 1; - }); let next_call_id = Arc::new(AtomicU32::new(0)); fake_language_server .handle_request::(move |params, _| { @@ -7992,37 +7944,77 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.foreground().finish_waiting(); cx_a.foreground().run_until_parked(); - fn extract_hint_labels(editor: &Editor) -> Vec { - let mut labels = Vec::new(); - for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { - let excerpt_hints = excerpt_hints.read(); - for (_, inlay) in excerpt_hints.hints.iter() { - match &inlay.label { - project::InlayHintLabel::String(s) => labels.push(s.to_string()), - _ => unreachable!(), - } - } - } - labels - } - + let mut edits_made = 0; + edits_made += 1; editor_a.update(cx_a, |editor, _| { assert_eq!( vec!["0".to_string()], extract_hint_labels(editor), - "Host should get hints from the 1st edit and 1st LSP query" + "Host should get its first hints when opens an editor" ); let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); assert_eq!( inlay_cache.version, edits_made, - "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + "Host editor should track its own inlay cache history, which should be incremented after every cache/view change" ); }); + let workspace_b = client_b.build_workspace(&project_b, cx_b); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + cx_b.foreground().run_until_parked(); editor_b.update(cx_b, |editor, _| { assert_eq!( vec!["0".to_string(), "1".to_string()], extract_hint_labels(editor), + "Client should get its first hints when opens an editor" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Client editor should track its own inlay cache history, which should be incremented after every cache/view change" + ); + }); + + editor_b.update(cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); + editor.handle_input(":", cx); + cx.focus(&editor_b); + edits_made += 1; + }); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".to_string(), "2".to_string()], + extract_hint_labels(editor), + "Host should get hints from the 1st edit and 1st LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!(inlay_cache.version, edits_made); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string()], + extract_hint_labels(editor), "Guest should get hints the 1st edit and 2nd LSP query" ); let inlay_cache = editor.inlay_hint_cache(); @@ -8043,7 +8035,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_b.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert_eq!( - vec!["0".to_string(), "1".to_string(), "2".to_string()], + vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string()], extract_hint_labels(editor), "Host should get hints from 3rd edit, 5th LSP query: \ 4th query was made by guest (but not applied) due to cache invalidation logic" @@ -8061,7 +8053,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( "0".to_string(), "1".to_string(), "2".to_string(), - "3".to_string() + "3".to_string(), + "4".to_string(), + "5".to_string(), ], extract_hint_labels(editor), "Guest should get hints from 3rd edit, 6th LSP query" @@ -8091,7 +8085,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( "1".to_string(), "2".to_string(), "3".to_string(), - "4".to_string() + "4".to_string(), + "5".to_string(), + "6".to_string(), ], extract_hint_labels(editor), "Host should react to /refresh LSP request and get new hints from 7th LSP query" @@ -8108,7 +8104,16 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); editor_b.update(cx_b, |editor, _| { assert_eq!( - vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()], + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string(), + "5".to_string(), + "6".to_string(), + "7".to_string(), + ], extract_hint_labels(editor), "Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query" ); @@ -8120,7 +8125,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( assert_eq!( inlay_cache.version, edits_made, - "Gues should accepted all edits and bump its cache version every time" + "Guest should accepted all edits and bump its cache version every time" ); }); } @@ -8148,3 +8153,17 @@ fn room_participants(room: &ModelHandle, cx: &mut TestAppContext) -> RoomP RoomParticipants { remote, pending } }) } + +fn extract_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + labels +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4644336bce2b96edb9ed5e5b35d3001e9f1be580..5d5bdd1db406bcb013baac67ac46274e9da8a465 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1393,7 +1393,6 @@ impl Editor { } this.report_editor_event("open", None, cx); - this.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); this } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6525e7fc222843fdfa04a94317f4a2a95f6dadcf..7936ed76cb85da078e61493b812c33d83a9a72b4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1921,7 +1921,7 @@ impl Element for EditorElement { let em_advance = style.text.em_advance(cx.font_cache()); let overscroll = vec2f(em_width, 0.); let snapshot = { - editor.set_visible_line_count(size.y() / line_height); + editor.set_visible_line_count(size.y() / line_height, cx); let editor_width = text_width - gutter_margin - overscroll.x() - em_width; let wrap_width = match editor.soft_wrap_mode(cx) { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index b0f329c87fb2d5d803d2d0c2630bcd48499be692..d595337428dfce0a8f8fa9d5ad6c001b8072ba3f 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -19,7 +19,8 @@ use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, persistence::DB, - Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, + Anchor, DisplayPoint, Editor, EditorMode, Event, InlayRefreshReason, MultiBufferSnapshot, + ToPoint, }; use self::{ @@ -293,8 +294,19 @@ impl Editor { self.scroll_manager.visible_line_count } - pub(crate) fn set_visible_line_count(&mut self, lines: f32) { + pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { + let opened_first_time = self.scroll_manager.visible_line_count.is_none(); self.scroll_manager.visible_line_count = Some(lines); + if opened_first_time { + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + editor.refresh_inlays(InlayRefreshReason::NewLinesShown, cx) + }) + .ok() + }) + .detach() + } } pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { @@ -322,7 +334,7 @@ impl Editor { ); if !self.is_singleton(cx) { - self.refresh_inlays(crate::InlayRefreshReason::NewLinesShown, cx); + self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx); } } From dfb30218ca8f919434eb0c7d36035bdbedd8a66b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 25 Jun 2023 17:09:34 +0300 Subject: [PATCH 145/169] Remove mutex usage from *Map contents --- crates/editor/src/display_map/block_map.rs | 4 ++-- crates/editor/src/display_map/inlay_map.rs | 11 +++++------ crates/editor/src/display_map/tab_map.rs | 17 ++++++++--------- crates/editor/src/display_map/wrap_map.rs | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 195962d0c2ab8fdc73431bfbd8029df1af8c9274..8f78c3885a6a288ebbb30fca6b2cbbb931648500 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1032,7 +1032,7 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); + let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1278,7 +1278,7 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); + let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 8639f6d091c21db3da897d3eaeca0a28a31d131a..4fc0c2fff42e0abb6ca55d0930f6644224d4e3b0 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -5,7 +5,6 @@ use crate::{ use collections::{BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; -use parking_lot::Mutex; use std::{ cmp, ops::{Add, AddAssign, Range, Sub, SubAssign}, @@ -14,7 +13,7 @@ use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; pub struct InlayMap { - snapshot: Mutex, + snapshot: InlaySnapshot, inlays_by_id: HashMap, inlays: Vec, } @@ -308,7 +307,7 @@ impl InlayMap { ( Self { - snapshot: Mutex::new(snapshot.clone()), + snapshot: snapshot.clone(), inlays_by_id: HashMap::default(), inlays: Vec::new(), }, @@ -321,7 +320,7 @@ impl InlayMap { buffer_snapshot: MultiBufferSnapshot, mut buffer_edits: Vec>, ) -> (InlaySnapshot, Vec) { - let mut snapshot = self.snapshot.lock(); + let mut snapshot = &mut self.snapshot; if buffer_edits.is_empty() { if snapshot.buffer.trailing_excerpt_update_count() @@ -456,7 +455,7 @@ impl InlayMap { to_remove: Vec, to_insert: Vec<(InlayId, InlayProperties)>, ) -> (InlaySnapshot, Vec) { - let snapshot = self.snapshot.lock(); + let snapshot = &mut self.snapshot; let mut edits = BTreeSet::new(); self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); @@ -521,7 +520,7 @@ impl InlayMap { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); - let snapshot = self.snapshot.lock(); + let snapshot = &mut self.snapshot; for _ in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { let position = snapshot.buffer.random_byte_range(0, rng).start; diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 9157caace496e72597e70ac039e7797672122dbc..eaaaeed8adb27b6cb9e529e575d616ede4c09028 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -5,13 +5,12 @@ use super::{ use crate::MultiBufferSnapshot; use gpui::fonts::HighlightStyle; use language::{Chunk, Point}; -use parking_lot::Mutex; use std::{cmp, mem, num::NonZeroU32, ops::Range}; use sum_tree::Bias; const MAX_EXPANSION_COLUMN: u32 = 256; -pub struct TabMap(Mutex); +pub struct TabMap(TabSnapshot); impl TabMap { pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { @@ -21,22 +20,22 @@ impl TabMap { max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, }; - (Self(Mutex::new(snapshot.clone())), snapshot) + (Self(snapshot.clone()), snapshot) } #[cfg(test)] - pub fn set_max_expansion_column(&self, column: u32) -> TabSnapshot { - self.0.lock().max_expansion_column = column; - self.0.lock().clone() + pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot { + self.0.max_expansion_column = column; + self.0.clone() } pub fn sync( - &self, + &mut self, fold_snapshot: FoldSnapshot, mut fold_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { - let mut old_snapshot = self.0.lock(); + let old_snapshot = &mut self.0; let mut new_snapshot = TabSnapshot { fold_snapshot, tab_size, @@ -711,7 +710,7 @@ mod tests { let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); + let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); let text = text::Rope::from(tabs_snapshot.text().as_str()); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 5197a2e0de49150f38e37e39ca7e42ba4050c026..0e7f1f816739facc01b1e9af1f00cda4b0ba31ef 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1090,7 +1090,7 @@ mod tests { log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); + let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); From 5c21ed42638b89b34a1e54f8ffcd361ed3a77b6b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 25 Jun 2023 17:25:25 +0300 Subject: [PATCH 146/169] Properly filter out task hints --- crates/editor/src/inlay_hint_cache.rs | 47 +++++++-------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 6de601f9b23049316c2053b3c777a6abcf9ba01c..d0274b516d32147baf0edda84fd58cc27305d892 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -82,33 +82,6 @@ impl UpdateTask { } } -impl ExcerptDimensions { - fn contains_position( - &self, - position: language::Anchor, - buffer_snapshot: &BufferSnapshot, - visible_only: bool, - ) -> bool { - if visible_only { - self.excerpt_visible_range_start - .cmp(&position, buffer_snapshot) - .is_le() - && self - .excerpt_visible_range_end - .cmp(&position, buffer_snapshot) - .is_ge() - } else { - self.excerpt_range_start - .cmp(&position, buffer_snapshot) - .is_le() - && self - .excerpt_range_end - .cmp(&position, buffer_snapshot) - .is_ge() - } - } -} - #[derive(Debug, Clone, Copy)] pub enum InvalidationStrategy { Forced, @@ -632,10 +605,7 @@ fn calculate_hint_updates( let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { - if !query - .dimensions - .contains_position(new_hint.position, buffer_snapshot, false) - { + if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) { continue; } let missing_from_cache = match &cached_excerpt_hints { @@ -674,11 +644,7 @@ fn calculate_hint_updates( .iter() .filter(|hint| hint.position.excerpt_id == query.excerpt_id) .filter(|hint| { - query.dimensions.contains_position( - hint.position.text_anchor, - buffer_snapshot, - false, - ) + contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot) }) .filter(|hint| { fetch_range @@ -830,3 +796,12 @@ pub fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( .current_inlays() .filter(|inlay| Some(inlay.id) != editor.copilot_state.suggestion.as_ref().map(|h| h.id)) } + +fn contains_position( + range: &Range, + position: language::Anchor, + buffer_snapshot: &BufferSnapshot, +) -> bool { + range.start.cmp(&position, buffer_snapshot).is_le() + && range.end.cmp(&position, buffer_snapshot).is_ge() +} From 976edfedf735bd8928e84acc75d62d09fce3440f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 23 Jun 2023 18:25:27 +0200 Subject: [PATCH 147/169] Add `Cursor::next_item` --- crates/sum_tree/src/cursor.rs | 36 +++++++++++++++++++++++++++++++++ crates/sum_tree/src/sum_tree.rs | 31 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index b78484e6ded5b69c09b081abc7a17d8084fbcdf3..59165283f6642fc2ea097113a61255cc98a6c25b 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/crates/sum_tree/src/cursor.rs @@ -97,6 +97,42 @@ where } } + pub fn next_item(&self) -> Option<&'a T> { + self.assert_did_seek(); + if let Some(entry) = self.stack.last() { + if entry.index == entry.tree.0.items().len() - 1 { + if let Some(next_leaf) = self.next_leaf() { + Some(next_leaf.0.items().first().unwrap()) + } else { + None + } + } else { + match *entry.tree.0 { + Node::Leaf { ref items, .. } => Some(&items[entry.index + 1]), + _ => unreachable!(), + } + } + } else if self.at_end { + None + } else { + self.tree.first() + } + } + + fn next_leaf(&self) -> Option<&'a SumTree> { + for entry in self.stack.iter().rev().skip(1) { + if entry.index < entry.tree.0.child_trees().len() - 1 { + match *entry.tree.0 { + Node::Internal { + ref child_trees, .. + } => return Some(child_trees[entry.index + 1].leftmost_leaf()), + Node::Leaf { .. } => unreachable!(), + }; + } + } + None + } + pub fn prev_item(&self) -> Option<&'a T> { self.assert_did_seek(); if let Some(entry) = self.stack.last() { diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 577a942889694790343600ff8151a6609961839c..6ac0ef6350f1bae2105e28f93036efd7349d3830 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -816,6 +816,14 @@ mod tests { assert_eq!(cursor.item(), None); } + if before_start { + assert_eq!(cursor.next_item(), reference_items.get(0)); + } else if pos + 1 < reference_items.len() { + assert_eq!(cursor.next_item().unwrap(), &reference_items[pos + 1]); + } else { + assert_eq!(cursor.next_item(), None); + } + if i < 5 { cursor.next(&()); if pos < reference_items.len() { @@ -861,14 +869,17 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.prev(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); // Single-element tree @@ -881,22 +892,26 @@ mod tests { ); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); let mut cursor = tree.cursor::(); assert_eq!(cursor.slice(&Count(1), Bias::Right, &()).items(&()), [1]); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); cursor.seek(&Count(0), Bias::Right, &()); @@ -908,6 +923,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); // Multiple-element tree @@ -918,67 +934,80 @@ mod tests { assert_eq!(cursor.slice(&Count(2), Bias::Right, &()).items(&()), [1, 2]); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.next_item(), Some(&4)); assert_eq!(cursor.start().sum, 3); cursor.next(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.next_item(), Some(&5)); assert_eq!(cursor.start().sum, 6); cursor.next(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.next_item(), Some(&6)); assert_eq!(cursor.start().sum, 10); cursor.next(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 15); cursor.next(&()); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); cursor.prev(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 15); cursor.prev(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.next_item(), Some(&6)); assert_eq!(cursor.start().sum, 10); cursor.prev(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.next_item(), Some(&5)); assert_eq!(cursor.start().sum, 6); cursor.prev(&()); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.next_item(), Some(&4)); assert_eq!(cursor.start().sum, 3); cursor.prev(&()); assert_eq!(cursor.item(), Some(&2)); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), Some(&3)); assert_eq!(cursor.start().sum, 1); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&2)); assert_eq!(cursor.start().sum, 0); cursor.prev(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&1)); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&2)); assert_eq!(cursor.start().sum, 0); let mut cursor = tree.cursor::(); @@ -990,6 +1019,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); cursor.seek(&Count(3), Bias::Right, &()); @@ -1001,6 +1031,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); // Seeking can bias left or right From f77b680db9e1d1ba6cecb533e566ee112078b756 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 23 Jun 2023 17:35:18 +0300 Subject: [PATCH 148/169] Account for inlay biases when clipping a point --- crates/editor/src/display_map/inlay_map.rs | 391 ++++++++++++++++++--- crates/project/src/lsp_command.rs | 110 +++--- crates/sum_tree/src/sum_tree.rs | 9 + 3 files changed, 415 insertions(+), 95 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 4fc0c2fff42e0abb6ca55d0930f6644224d4e3b0..effd0576ef39b39605bb54520888b0cd4c52ee39 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -301,7 +301,7 @@ impl InlayMap { let version = 0; let snapshot = InlaySnapshot { buffer: buffer.clone(), - transforms: SumTree::from_item(Transform::Isomorphic(buffer.text_summary()), &()), + transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), version, }; @@ -357,7 +357,7 @@ impl InlayMap { new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); if let Some(Transform::Isomorphic(transform)) = cursor.item() { if cursor.end(&()).0 == buffer_edit.old.start { - new_transforms.push(Transform::Isomorphic(transform.clone()), &()); + push_isomorphic(&mut new_transforms, transform.clone()); cursor.next(&()); } } @@ -436,7 +436,7 @@ impl InlayMap { } new_transforms.append(cursor.suffix(&()), &()); - if new_transforms.first().is_none() { + if new_transforms.is_empty() { new_transforms.push(Transform::Isomorphic(Default::default()), &()); } @@ -654,55 +654,124 @@ impl InlaySnapshot { pub fn to_inlay_point(&self, point: Point) -> InlayPoint { let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(); cursor.seek(&point, Bias::Left, &()); - match cursor.item() { - Some(Transform::Isomorphic(_)) => { - let overshoot = point - cursor.start().0; - InlayPoint(cursor.start().1 .0 + overshoot) + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if point == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = point - cursor.start().0; + return InlayPoint(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.max_point(); + } } - Some(Transform::Inlay(_)) => cursor.start().1, - None => self.max_point(), } } - pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { + pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); cursor.seek(&point, Bias::Left, &()); - - let mut bias = bias; - let mut skipped_inlay = false; loop { match cursor.item() { Some(Transform::Isomorphic(transform)) => { - let overshoot = if skipped_inlay { - match bias { - Bias::Left => transform.lines, - Bias::Right => { - if transform.first_line_chars == 0 { - Point::new(1, 0) - } else { - Point::new(0, 1) - } + if cursor.start().0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.prev_item() { + if inlay.position.bias() == Bias::Left { + return point; + } else if bias == Bias::Left { + cursor.prev(&()); + } else if transform.first_line_chars == 0 { + point.0 += Point::new(1, 0); + } else { + point.0 += Point::new(0, 1); + } + } else { + return point; + } + } else if cursor.end(&()).0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + return point; + } else if bias == Bias::Right { + cursor.next(&()); + } else if point.0.column == 0 { + point.0.row -= 1; + point.0.column = self.line_len(point.0.row); + } else { + point.0.column -= 1; } + } else { + return point; } } else { - point.0 - cursor.start().0 .0 - }; - let buffer_point = cursor.start().1 + overshoot; - let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); - let clipped_overshoot = clipped_buffer_point - cursor.start().1; - return InlayPoint(cursor.start().0 .0 + clipped_overshoot); + let overshoot = point.0 - cursor.start().0 .0; + let buffer_point = cursor.start().1 + overshoot; + let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); + let clipped_overshoot = clipped_buffer_point - cursor.start().1; + let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot); + if clipped_point == point { + return clipped_point; + } else { + point = clipped_point; + } + } } - Some(Transform::Inlay(_)) => skipped_inlay = true, - None => match bias { - Bias::Left => return Default::default(), - Bias::Right => bias = Bias::Left, - }, - } + Some(Transform::Inlay(inlay)) => { + if point == cursor.start().0 && inlay.position.bias() == Bias::Right { + match cursor.prev_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + return point; + } + } + _ => return point, + } + } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left { + match cursor.next_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Right { + return point; + } + } + _ => return point, + } + } - if bias == Bias::Left { - cursor.prev(&()); - } else { - cursor.next(&()); + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } + None => { + bias = bias.invert(); + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } } } } @@ -833,6 +902,18 @@ impl InlaySnapshot { #[cfg(any(debug_assertions, feature = "test-support"))] { assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); + let mut transforms = self.transforms.iter().peekable(); + while let Some(transform) = transforms.next() { + let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_)); + if let Some(next_transform) = transforms.peek() { + let next_transform_is_isomorphic = + matches!(next_transform, Transform::Isomorphic(_)); + assert!( + !transform_is_isomorphic || !next_transform_is_isomorphic, + "two adjacent isomorphic transforms" + ); + } + } } } } @@ -983,6 +1064,177 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right), + InlayPoint::new(0, 1) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right), + InlayPoint::new(0, 2) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right), + InlayPoint::new(0, 9) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right), + InlayPoint::new(0, 10) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right), + InlayPoint::new(0, 11) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left), + InlayPoint::new(0, 17) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left), + InlayPoint::new(0, 18) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right), + InlayPoint::new(0, 18) + ); + // The inlays can be manually removed. let (inlay_snapshot, _) = inlay_map .splice::(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); @@ -1146,6 +1398,41 @@ mod tests { assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); assert_eq!(expected_text.len(), inlay_snapshot.len().0); + let mut buffer_point = Point::default(); + let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + let mut buffer_chars = buffer_snapshot.chars_at(0); + loop { + // No matter which bias we clip an inlay point with, it doesn't move + // because it was constructed from a buffer point. + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Left), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped left", + buffer_point + ); + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Right), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped right", + buffer_point + ); + + if let Some(ch) = buffer_chars.next() { + if ch == '\n' { + buffer_point += Point::new(1, 0); + } else { + buffer_point += Point::new(0, ch.len_utf8() as u32); + } + + // Ensure that moving forward in the buffer always moves the inlay point forward as well. + let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + assert!(new_inlay_point > inlay_point); + inlay_point = new_inlay_point; + } else { + break; + } + } + let mut inlay_point = InlayPoint::default(); let mut inlay_offset = InlayOffset::default(); for ch in expected_text.chars() { @@ -1161,13 +1448,6 @@ mod tests { "invalid to_point({:?})", inlay_offset ); - assert_eq!( - inlay_snapshot.to_inlay_point(inlay_snapshot.to_buffer_point(inlay_point)), - inlay_snapshot.clip_point(inlay_point, Bias::Left), - "to_buffer_point({:?}) = {:?}", - inlay_point, - inlay_snapshot.to_buffer_point(inlay_point), - ); let mut bytes = [0; 4]; for byte in ch.encode_utf8(&mut bytes).as_bytes() { @@ -1182,7 +1462,8 @@ mod tests { let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right); assert!( clipped_left_point <= clipped_right_point, - "clipped left point {:?} is greater than clipped right point {:?}", + "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})", + inlay_point, clipped_left_point, clipped_right_point ); @@ -1200,6 +1481,24 @@ mod tests { // Ensure the clipped points never overshoot the end of the map. assert!(clipped_left_point <= inlay_snapshot.max_point()); assert!(clipped_right_point <= inlay_snapshot.max_point()); + + // Ensure the clipped points are at valid buffer locations. + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)), + clipped_left_point, + "to_buffer_point({:?}) = {:?}", + clipped_left_point, + inlay_snapshot.to_buffer_point(clipped_left_point), + ); + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)), + clipped_right_point, + "to_buffer_point({:?}) = {:?}", + clipped_right_point, + inlay_snapshot.to_buffer_point(clipped_right_point), + ); } } } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index a7b6e08e913ee4b12a987469cfe7db0218482b91..674ee63349ab73e2c571874662a2822a37e3310f 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1832,22 +1832,34 @@ impl LspCommand for InlayHints { Ok(message .unwrap_or_default() .into_iter() - .map(|lsp_hint| InlayHint { - buffer_id: origin_buffer.remote_id(), - position: origin_buffer.anchor_after( - origin_buffer - .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), - ), - padding_left: lsp_hint.padding_left.unwrap_or(false), - padding_right: lsp_hint.padding_right.unwrap_or(false), - label: match lsp_hint.label { - lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), - lsp::InlayHintLabel::LabelParts(lsp_parts) => InlayHintLabel::LabelParts( - lsp_parts - .into_iter() - .map(|label_part| InlayHintLabelPart { - value: label_part.value, - tooltip: label_part.tooltip.map(|tooltip| match tooltip { + .map(|lsp_hint| { + let kind = lsp_hint.kind.and_then(|kind| match kind { + lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), + lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), + _ => None, + }); + let position = origin_buffer + .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left); + InlayHint { + buffer_id: origin_buffer.remote_id(), + position: if kind == Some(InlayHintKind::Parameter) { + origin_buffer.anchor_before(position) + } else { + origin_buffer.anchor_after(position) + }, + padding_left: lsp_hint.padding_left.unwrap_or(false), + padding_right: lsp_hint.padding_right.unwrap_or(false), + label: match lsp_hint.label { + lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), + lsp::InlayHintLabel::LabelParts(lsp_parts) => { + InlayHintLabel::LabelParts( + lsp_parts + .into_iter() + .map(|label_part| InlayHintLabelPart { + value: label_part.value, + tooltip: label_part.tooltip.map( + |tooltip| { + match tooltip { lsp::InlayHintLabelPartTooltip::String(s) => { InlayHintLabelPartTooltip::String(s) } @@ -1859,40 +1871,40 @@ impl LspCommand for InlayHints { value: markup_content.value, }, ), - }), - location: label_part.location.map(|lsp_location| { - let target_start = origin_buffer.clip_point_utf16( - point_from_lsp(lsp_location.range.start), - Bias::Left, - ); - let target_end = origin_buffer.clip_point_utf16( - point_from_lsp(lsp_location.range.end), - Bias::Left, - ); - Location { - buffer: buffer.clone(), - range: origin_buffer.anchor_after(target_start) - ..origin_buffer.anchor_before(target_end), - } - }), + } + }, + ), + location: label_part.location.map(|lsp_location| { + let target_start = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.start), + Bias::Left, + ); + let target_end = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.end), + Bias::Left, + ); + Location { + buffer: buffer.clone(), + range: origin_buffer.anchor_after(target_start) + ..origin_buffer.anchor_before(target_end), + } + }), + }) + .collect(), + ) + } + }, + kind, + tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { + lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), + lsp::InlayHintTooltip::MarkupContent(markup_content) => { + InlayHintTooltip::MarkupContent(MarkupContent { + kind: format!("{:?}", markup_content.kind), + value: markup_content.value, }) - .collect(), - ), - }, - kind: lsp_hint.kind.and_then(|kind| match kind { - lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), - lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), - _ => None, - }), - tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { - lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), - lsp::InlayHintTooltip::MarkupContent(markup_content) => { - InlayHintTooltip::MarkupContent(MarkupContent { - kind: format!("{:?}", markup_content.kind), - value: markup_content.value, - }) - } - }), + } + }), + } }) .collect()) }) diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 6ac0ef6350f1bae2105e28f93036efd7349d3830..8d219ca02110827d4c76fe170f6c541651148170 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -102,6 +102,15 @@ pub enum Bias { Right, } +impl Bias { + pub fn invert(self) -> Self { + match self { + Self::Left => Self::Right, + Self::Right => Self::Left, + } + } +} + #[derive(Debug, Clone)] pub struct SumTree(Arc>); From 096bad1f73933a3c7783c3d2e3d156f299ac71ec Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 13:52:09 +0300 Subject: [PATCH 149/169] Revert useless changes, simplify --- crates/collab/src/tests/integration_tests.rs | 4 +- crates/editor/Cargo.toml | 3 +- crates/editor/src/editor.rs | 61 +++++++++----------- crates/editor/src/inlay_hint_cache.rs | 13 +---- crates/editor/src/multi_buffer.rs | 1 - 5 files changed, 31 insertions(+), 51 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 5ff7f09ff88ddb58734c5794b3e719d6784e3629..8095b39a7f8e98a695c85d8e59bae4d8a231129d 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -6406,7 +6406,6 @@ async fn test_basic_following( let client_b = server.create_client(cx_b, "user_b").await; let client_c = server.create_client(cx_c, "user_c").await; let client_d = server.create_client(cx_d, "user_d").await; - server .create_room(&mut [ (&client_a, cx_a), @@ -7944,8 +7943,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.foreground().finish_waiting(); cx_a.foreground().run_until_parked(); - let mut edits_made = 0; - edits_made += 1; + let mut edits_made = 1; editor_a.update(cx_a, |editor, _| { assert_eq!( vec!["0".to_string()], diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 61145e40ff15aacb9a0df10c3f9eb31f5c487244..dcc22202273a5d087a5892f279e1d153b3d9770e 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [features] test-support = [ + "rand", "copilot/test-support", "text/test-support", "language/test-support", @@ -56,7 +57,7 @@ ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } -rand = { workspace = true } +rand = { workspace = true, optional = true } schemars.workspace = true serde.workspace = true serde_derive.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5d5bdd1db406bcb013baac67ac46274e9da8a465..65beec6d973e1bd8813a6ef840c930f853fb57eb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,5 +1,4 @@ mod blink_manager; - pub mod display_map; mod editor_settings; mod element; @@ -54,7 +53,7 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; -use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice, InvalidationStrategy}; +use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; @@ -2617,7 +2616,7 @@ impl Editor { let new_splice = self.inlay_hint_cache.update_settings( &self.buffer, new_settings, - visible_inlay_hints(self, cx).cloned().collect(), + self.visible_inlay_hints(cx), cx, ); if let Some(InlaySplice { @@ -2648,6 +2647,17 @@ impl Editor { } } + fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec { + self.display_map + .read(cx) + .current_inlays() + .filter(move |inlay| { + Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id) + }) + .cloned() + .collect() + } + fn excerpt_visible_offsets( &self, cx: &mut ViewContext<'_, '_, Editor>, @@ -5659,7 +5669,6 @@ impl Editor { } } - // TODO: Handle selections that cross excerpts // TODO: Handle selections that cross excerpts for selection in &mut selections { let start_column = snapshot.indent_size_for_line(selection.start.row).len; @@ -7257,37 +7266,21 @@ impl Editor { buffer, predecessor, excerpts, - } => { - cx.emit(Event::ExcerptsAdded { - buffer: buffer.clone(), - predecessor: *predecessor, - excerpts: excerpts.clone(), - }); - } + } => cx.emit(Event::ExcerptsAdded { + buffer: buffer.clone(), + predecessor: *predecessor, + excerpts: excerpts.clone(), + }), multi_buffer::Event::ExcerptsRemoved { ids } => { - cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }); - } - multi_buffer::Event::Reparsed => { - cx.emit(Event::Reparsed); - } - multi_buffer::Event::DirtyChanged => { - cx.emit(Event::DirtyChanged); - } - multi_buffer::Event::Saved => { - cx.emit(Event::Saved); - } - multi_buffer::Event::FileHandleChanged => { - cx.emit(Event::TitleChanged); - } - multi_buffer::Event::Reloaded => { - cx.emit(Event::TitleChanged); - } - multi_buffer::Event::DiffBaseChanged => { - cx.emit(Event::DiffBaseChanged); - } - multi_buffer::Event::Closed => { - cx.emit(Event::Closed); - } + cx.emit(Event::ExcerptsRemoved { ids: ids.clone() }) + } + multi_buffer::Event::Reparsed => cx.emit(Event::Reparsed), + multi_buffer::Event::DirtyChanged => cx.emit(Event::DirtyChanged), + multi_buffer::Event::Saved => cx.emit(Event::Saved), + multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged), + multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged), + multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged), + multi_buffer::Event::Closed => cx.emit(Event::Closed), multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d0274b516d32147baf0edda84fd58cc27305d892..bee08f6cc736d007e00fd11f279663566b61780b 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -306,7 +306,7 @@ fn spawn_new_update_tasks( update_cache_version: usize, cx: &mut ViewContext<'_, '_, Editor>, ) { - let visible_hints = Arc::new(visible_inlay_hints(editor, cx).cloned().collect::>()); + let visible_hints = Arc::new(editor.visible_inlay_hints(cx)); for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query { if !excerpt_visible_range.is_empty() { let buffer = buffer_handle.read(cx); @@ -786,17 +786,6 @@ fn hints_fetch_tasks( } } -pub fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>( - editor: &'a Editor, - cx: &'b ViewContext<'c, 'd, Editor>, -) -> impl Iterator + 'a { - editor - .display_map - .read(cx) - .current_inlays() - .filter(|inlay| Some(inlay.id) != editor.copilot_state.suggestion.as_ref().map(|h| h.id)) -} - fn contains_position( range: &Range, position: language::Anchor, diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index d4298efacf1c4f5a051fd94422cbc62d90f2275d..c5070363eb7e2c4bc3910b4444c2195a584f8e4d 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2631,7 +2631,6 @@ impl MultiBufferSnapshot { }; } } - panic!("excerpt not found") } From 67214f0e5520d3b5a6c3b05d95e7c771b115650e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 16:32:53 +0300 Subject: [PATCH 150/169] Only skip /refresh inlay queries when vislble range is not updated --- crates/editor/src/inlay_hint_cache.rs | 359 +++++++++++++------------- 1 file changed, 175 insertions(+), 184 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index bee08f6cc736d007e00fd11f279663566b61780b..b1f9fdf5158517563e817d1cd7d947d48b38eff8 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -57,6 +57,39 @@ struct ExcerptDimensions { excerpt_visible_range_end: language::Anchor, } +impl ExcerptQuery { + fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges { + let visible_range = + self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end; + let mut other_ranges = Vec::new(); + if self + .dimensions + .excerpt_range_start + .cmp(&self.dimensions.excerpt_visible_range_start, buffer) + .is_lt() + { + let mut end = self.dimensions.excerpt_visible_range_start; + end.offset -= 1; + other_ranges.push(self.dimensions.excerpt_range_start..end); + } + if self + .dimensions + .excerpt_range_end + .cmp(&self.dimensions.excerpt_visible_range_end, buffer) + .is_gt() + { + let mut start = self.dimensions.excerpt_visible_range_end; + start.offset += 1; + other_ranges.push(start..self.dimensions.excerpt_range_end); + } + + HintFetchRanges { + visible_range, + other_ranges: other_ranges.into_iter().map(|range| range).collect(), + } + } +} + impl UpdateTask { fn new(invalidation_strategy: InvalidationStrategy, spawned_task: SpawnedTask) -> Self { Self { @@ -427,17 +460,18 @@ fn new_update_task( buffer_snapshot: BufferSnapshot, visible_hints: Arc>, cached_excerpt_hints: Option>>, - previous_task: Option>, + task_before_refresh: Option>, cx: &mut ViewContext<'_, '_, Editor>, ) -> SpawnedTask { - let hints_fetch_tasks = hints_fetch_tasks(query, &buffer_snapshot, cx); + let hints_fetch_tasks = query.hints_fetch_ranges(&buffer_snapshot); let (is_running_tx, is_running_rx) = smol::channel::bounded(1); + let is_refresh_task = task_before_refresh.is_some(); let _task = cx.spawn(|editor, cx| async move { let _is_running_tx = is_running_tx; - if let Some(previous_task) = previous_task { - previous_task.recv().await.ok(); + if let Some(task_before_refresh) = task_before_refresh { + task_before_refresh.recv().await.ok(); } - let create_update_task = |range, hint_fetch_task| { + let create_update_task = |range| { fetch_and_update_hints( editor.clone(), multi_buffer_snapshot.clone(), @@ -446,34 +480,47 @@ fn new_update_task( cached_excerpt_hints.as_ref().map(Arc::clone), query, range, - hint_fetch_task, cx.clone(), ) }; - let (visible_range, visible_range_hint_fetch_task) = hints_fetch_tasks.visible_range; - let visible_range_has_updates = - match create_update_task(visible_range, visible_range_hint_fetch_task).await { - Ok(updated) => updated, - Err(e) => { - error!("inlay hint visible range update task failed: {e:#}"); - return; - } - }; + if is_refresh_task { + let visible_range_has_updates = + match create_update_task(hints_fetch_tasks.visible_range).await { + Ok(updated) => updated, + Err(e) => { + error!("inlay hint visible range update task failed: {e:#}"); + return; + } + }; - if visible_range_has_updates { - let other_update_results = - futures::future::join_all(hints_fetch_tasks.other_ranges.into_iter().map( - |(fetch_range, hints_fetch_task)| { - create_update_task(fetch_range, hints_fetch_task) - }, - )) + if visible_range_has_updates { + let other_update_results = futures::future::join_all( + hints_fetch_tasks + .other_ranges + .into_iter() + .map(create_update_task), + ) .await; - for result in other_update_results { + for result in other_update_results { + if let Err(e) = result { + error!("inlay hint update task failed: {e:#}"); + return; + } + } + } + } else { + let task_update_results = futures::future::join_all( + std::iter::once(hints_fetch_tasks.visible_range) + .chain(hints_fetch_tasks.other_ranges.into_iter()) + .map(create_update_task), + ) + .await; + + for result in task_update_results { if let Err(e) = result { error!("inlay hint update task failed: {e:#}"); - return; } } } @@ -494,100 +541,112 @@ async fn fetch_and_update_hints( cached_excerpt_hints: Option>>, query: ExcerptQuery, fetch_range: Range, - hints_fetch_task: Task>>>, mut cx: gpui::AsyncAppContext, ) -> anyhow::Result { - let mut update_happened = false; - match hints_fetch_task.await.context("inlay hint fetch task")? { - Some(new_hints) => { - let background_task_buffer_snapshot = buffer_snapshot.clone(); - let backround_fetch_range = fetch_range.clone(); - if let Some(new_update) = cx - .background() - .spawn(async move { - calculate_hint_updates( - query, - backround_fetch_range, - new_hints, - &background_task_buffer_snapshot, - cached_excerpt_hints, - &visible_hints, - ) + let inlay_hints_fetch_task = editor + .update(&mut cx, |editor, cx| { + editor + .buffer() + .read(cx) + .buffer(query.buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.inlay_hints(buffer, fetch_range.clone(), cx) + })) }) - .await - { - update_happened = !new_update.add_to_cache.is_empty() - || !new_update.remove_from_cache.is_empty() - || !new_update.remove_from_visible.is_empty(); - editor - .update(&mut cx, |editor, cx| { - let cached_excerpt_hints = editor - .inlay_hint_cache - .hints - .entry(new_update.excerpt_id) - .or_insert_with(|| { - Arc::new(RwLock::new(CachedExcerptHints { - version: new_update.cache_version, - buffer_version: buffer_snapshot.version().clone(), - hints: Vec::new(), - })) - }); - let mut cached_excerpt_hints = cached_excerpt_hints.write(); - match new_update.cache_version.cmp(&cached_excerpt_hints.version) { - cmp::Ordering::Less => return, - cmp::Ordering::Greater | cmp::Ordering::Equal => { - cached_excerpt_hints.version = new_update.cache_version; - } - } - cached_excerpt_hints - .hints - .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id)); - cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); - editor.inlay_hint_cache.version += 1; - - let mut splice = InlaySplice { - to_remove: new_update.remove_from_visible, - to_insert: Vec::new(), - }; - - for new_hint in new_update.add_to_cache { - let new_hint_position = multi_buffer_snapshot - .anchor_in_excerpt(query.excerpt_id, new_hint.position); - let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); - if editor - .inlay_hint_cache - .allowed_hint_kinds - .contains(&new_hint.kind) - { - splice.to_insert.push(( - new_hint_position, - new_inlay_id, - new_hint.clone(), - )); - } + }) + .ok() + .flatten(); + let mut update_happened = false; + let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) }; + + let new_hints = inlay_hints_fetch_task + .await + .context("inlay hint fetch task")?; + let background_task_buffer_snapshot = buffer_snapshot.clone(); + let backround_fetch_range = fetch_range.clone(); + if let Some(new_update) = cx + .background() + .spawn(async move { + calculate_hint_updates( + query, + backround_fetch_range, + new_hints, + &background_task_buffer_snapshot, + cached_excerpt_hints, + &visible_hints, + ) + }) + .await + { + update_happened = !new_update.add_to_cache.is_empty() + || !new_update.remove_from_cache.is_empty() + || !new_update.remove_from_visible.is_empty(); + editor + .update(&mut cx, |editor, cx| { + let cached_excerpt_hints = editor + .inlay_hint_cache + .hints + .entry(new_update.excerpt_id) + .or_insert_with(|| { + Arc::new(RwLock::new(CachedExcerptHints { + version: new_update.cache_version, + buffer_version: buffer_snapshot.version().clone(), + hints: Vec::new(), + })) + }); + let mut cached_excerpt_hints = cached_excerpt_hints.write(); + match new_update.cache_version.cmp(&cached_excerpt_hints.version) { + cmp::Ordering::Less => return, + cmp::Ordering::Greater | cmp::Ordering::Equal => { + cached_excerpt_hints.version = new_update.cache_version; + } + } + cached_excerpt_hints + .hints + .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id)); + cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); + editor.inlay_hint_cache.version += 1; - cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); - } + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + to_insert: Vec::new(), + }; - cached_excerpt_hints - .hints - .sort_by(|(_, hint_a), (_, hint_b)| { - hint_a.position.cmp(&hint_b.position, &buffer_snapshot) - }); - drop(cached_excerpt_hints); - - let InlaySplice { - to_remove, - to_insert, - } = splice; - if !to_remove.is_empty() || !to_insert.is_empty() { - editor.splice_inlay_hints(to_remove, to_insert, cx) - } - }) - .ok(); - } - } - None => {} + for new_hint in new_update.add_to_cache { + let new_hint_position = multi_buffer_snapshot + .anchor_in_excerpt(query.excerpt_id, new_hint.position); + let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + splice + .to_insert + .push((new_hint_position, new_inlay_id, new_hint.clone())); + } + + cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); + } + + cached_excerpt_hints + .hints + .sort_by(|(_, hint_a), (_, hint_b)| { + hint_a.position.cmp(&hint_b.position, &buffer_snapshot) + }); + drop(cached_excerpt_hints); + + let InlaySplice { + to_remove, + to_insert, + } = splice; + if !to_remove.is_empty() || !to_insert.is_empty() { + editor.splice_inlay_hints(to_remove, to_insert, cx) + } + }) + .ok(); } Ok(update_happened) @@ -713,77 +772,9 @@ fn allowed_hint_types( new_allowed_hint_types } -struct HintFetchTasks { - visible_range: ( - Range, - Task>>>, - ), - other_ranges: Vec<( - Range, - Task>>>, - )>, -} - -fn hints_fetch_tasks( - query: ExcerptQuery, - buffer: &BufferSnapshot, - cx: &mut ViewContext<'_, '_, Editor>, -) -> HintFetchTasks { - let visible_range = - query.dimensions.excerpt_visible_range_start..query.dimensions.excerpt_visible_range_end; - let mut other_ranges = Vec::new(); - if query - .dimensions - .excerpt_range_start - .cmp(&query.dimensions.excerpt_visible_range_start, buffer) - .is_lt() - { - let mut end = query.dimensions.excerpt_visible_range_start; - end.offset -= 1; - other_ranges.push(query.dimensions.excerpt_range_start..end); - } - if query - .dimensions - .excerpt_range_end - .cmp(&query.dimensions.excerpt_visible_range_end, buffer) - .is_gt() - { - let mut start = query.dimensions.excerpt_visible_range_end; - start.offset += 1; - other_ranges.push(start..query.dimensions.excerpt_range_end); - } - - let mut query_task_for_range = |range_to_query| { - cx.spawn(|editor, mut cx| async move { - let task = editor - .update(&mut cx, |editor, cx| { - editor - .buffer() - .read(cx) - .buffer(query.buffer_id) - .and_then(|buffer| { - let project = editor.project.as_ref()?; - Some(project.update(cx, |project, cx| { - project.inlay_hints(buffer, range_to_query, cx) - })) - }) - }) - .ok() - .flatten(); - anyhow::Ok(match task { - Some(task) => Some(task.await.context("inlays for buffer task")?), - None => None, - }) - }) - }; - - HintFetchTasks { - visible_range: (visible_range.clone(), query_task_for_range(visible_range)), - other_ranges: other_ranges - .into_iter() - .map(|range| (range.clone(), query_task_for_range(range))) - .collect(), - } +struct HintFetchRanges { + visible_range: Range, + other_ranges: Vec>, } fn contains_position( From 143a020694d6b4801a8689a02a8348e567c234b4 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 26 Jun 2023 12:48:22 -0400 Subject: [PATCH 151/169] Update Hint Style zzz --- crates/editor/src/element.rs | 2 +- crates/theme/src/theme.rs | 1 + styles/src/styleTree/editor.ts | 0 styles/src/style_tree/editor.ts | 1 + styles/src/theme/syntax.ts | 17 +++++++++++++++++ 5 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 styles/src/styleTree/editor.ts diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7936ed76cb85da078e61493b812c33d83a9a72b4..928df1027f5624af19aba6dc5bc2936c647da2ed 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1392,7 +1392,7 @@ impl EditorElement { } else { let style = &self.style; let chunks = snapshot - .chunks(rows.clone(), true, Some(style.theme.suggestion)) + .chunks(rows.clone(), true, Some(style.theme.hint)) .map(|chunk| { let mut highlight_style = chunk .syntax_highlight_id diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0a62459a3a894ccd41a612d29b3a573df35d22ee..e54dcdfd1e987eaf24656bc735079db54d37f0bc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -689,6 +689,7 @@ pub struct Editor { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, + pub hint: HighlightStyle, pub suggestion: HighlightStyle, pub diagnostic_path_header: DiagnosticPathHeader, pub diagnostic_header: DiagnosticHeader, diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index aeb84f678d41e5f4304d436ef0deec46dc5a6b62..af58276d162acc741a22079d8cb59b3885e2b6dc 100644 --- a/styles/src/style_tree/editor.ts +++ b/styles/src/style_tree/editor.ts @@ -53,6 +53,7 @@ export default function editor(theme: ColorScheme): any { active_line_background: with_opacity(background(layer, "on"), 0.75), highlighted_line_background: background(layer, "on"), // Inline autocomplete suggestions, Co-pilot suggestions, etc. + hint: syntax.hint, suggestion: syntax.predictive, code_actions: { indicator: toggleable({ diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index 148d600713e6b92db3689a3e7d181f6c9b31332f..bfd3bd01382ca6a18700aab28363ae583b0520aa 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -17,6 +17,7 @@ export interface Syntax { "comment.doc": SyntaxHighlightStyle primary: SyntaxHighlightStyle predictive: SyntaxHighlightStyle + hint: SyntaxHighlightStyle // === Formatted Text ====== / emphasis: SyntaxHighlightStyle @@ -146,12 +147,23 @@ function build_default_syntax(color_scheme: ColorScheme): Syntax { "lch" ) .hex() + // Mix the neutral and green colors to get a + // hint color distinct from any other color in the theme + const hint = chroma + .mix( + color_scheme.ramps.neutral(0.6).hex(), + color_scheme.ramps.blue(0.4).hex(), + 0.45, + "lch" + ) + .hex() const color = { primary: color_scheme.ramps.neutral(1).hex(), comment: color_scheme.ramps.neutral(0.71).hex(), punctuation: color_scheme.ramps.neutral(0.86).hex(), predictive: predictive, + hint: hint, emphasis: color_scheme.ramps.blue(0.5).hex(), string: color_scheme.ramps.orange(0.5).hex(), function: color_scheme.ramps.yellow(0.5).hex(), @@ -183,6 +195,11 @@ function build_default_syntax(color_scheme: ColorScheme): Syntax { color: color.predictive, italic: true, }, + hint: { + color: color.hint, + weight: font_weights.bold, + // italic: true, + }, emphasis: { color: color.emphasis, }, From 2c54d926ea9da0568aa3ac6da513f5d7e36c7cd9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 20:12:02 +0300 Subject: [PATCH 152/169] Test inlay hint cache --- crates/collab/src/tests/integration_tests.rs | 36 +- crates/editor/src/inlay_hint_cache.rs | 338 ++++++++++++++++++- 2 files changed, 354 insertions(+), 20 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 8095b39a7f8e98a695c85d8e59bae4d8a231129d..bc8f7f4353b249d2120fd26e183905e7282f67af 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7957,7 +7957,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( ); assert_eq!( inlay_cache.version, edits_made, - "Host editor should track its own inlay cache history, which should be incremented after every cache/view change" + "Host editor update the cache version after every cache/view change", ); }); let workspace_b = client_b.build_workspace(&project_b, cx_b); @@ -7984,7 +7984,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( ); assert_eq!( inlay_cache.version, edits_made, - "Client editor should track its own inlay cache history, which should be incremented after every cache/view change" + "Guest editor update the cache version after every cache/view change" ); }); @@ -8011,16 +8011,21 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); editor_b.update(cx_b, |editor, _| { assert_eq!( - vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string()], + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string() + ], extract_hint_labels(editor), "Guest should get hints the 1st edit and 2nd LSP query" ); let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); assert_eq!( - inlay_cache.version, edits_made, - "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" ); + assert_eq!(inlay_cache.version, edits_made); }); editor_a.update(cx_a, |editor, cx| { @@ -8033,17 +8038,23 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_b.foreground().run_until_parked(); editor_a.update(cx_a, |editor, _| { assert_eq!( - vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string(), "4".to_string()], + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string() + ], extract_hint_labels(editor), "Host should get hints from 3rd edit, 5th LSP query: \ 4th query was made by guest (but not applied) due to cache invalidation logic" ); let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test"); assert_eq!( - inlay_cache.version, edits_made, - "Each editor should track its own inlay cache history, which should be incremented after every cache/view change" + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" ); + assert_eq!(inlay_cache.version, edits_made); }); editor_b.update(cx_b, |editor, _| { assert_eq!( @@ -8063,10 +8074,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( inlay_cache.allowed_hint_kinds, allowed_hint_kinds, "Inlay kinds settings never change during the test" ); - assert_eq!( - inlay_cache.version, edits_made, - "Guest should have a version increment" - ); + assert_eq!(inlay_cache.version, edits_made); }); fake_language_server diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index b1f9fdf5158517563e817d1cd7d947d48b38eff8..9c686c7980986d487f3efb5efa0ebdc25d1f3a02 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -134,7 +134,7 @@ struct ExcerptHintsUpdate { cache_version: usize, remove_from_visible: Vec, remove_from_cache: HashSet, - add_to_cache: Vec, + add_to_cache: HashSet, } impl InlayHintCache { @@ -413,14 +413,13 @@ fn spawn_new_update_tasks( let update_task = o.get_mut(); if update_task.is_running() { match (update_task.invalidation_strategy(), invalidation_strategy) { - (InvalidationStrategy::Forced, InvalidationStrategy::Forced) + (InvalidationStrategy::Forced, _) | (_, InvalidationStrategy::OnConflict) => { o.insert(UpdateTask::new( invalidation_strategy, new_update_task(None), )); } - (InvalidationStrategy::Forced, _) => {} (_, InvalidationStrategy::Forced) => { if cache_is_empty { o.insert(UpdateTask::new( @@ -660,8 +659,7 @@ fn calculate_hint_updates( cached_excerpt_hints: Option>>, visible_hints: &[Inlay], ) -> Option { - let mut add_to_cache: Vec = Vec::new(); - + let mut add_to_cache: HashSet = HashSet::default(); let mut excerpt_hints_to_persist = HashMap::default(); for new_hint in new_excerpt_hints { if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) { @@ -688,7 +686,7 @@ fn calculate_hint_updates( None => true, }; if missing_from_cache { - add_to_cache.push(new_hint); + add_to_cache.insert(new_hint); } } @@ -785,3 +783,331 @@ fn contains_position( range.start.cmp(&position, buffer_snapshot).is_le() && range.end.cmp(&position, buffer_snapshot).is_ge() } + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicU32, Ordering}; + + use crate::serde_json::json; + use futures::StreamExt; + use gpui::{TestAppContext, ViewHandle}; + use language::{ + language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, + }; + use lsp::FakeLanguageServer; + use project::{FakeFs, Project}; + use settings::SettingsStore; + use workspace::Workspace; + + use crate::{editor_tests::update_test_settings, EditorSettings}; + + use super::*; + + #[gpui::test] + async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + let (file_with_hints, editor, fake_server) = + prepare_test_objects(cx, &allowed_hint_kinds).await; + + let lsp_request_count = Arc::new(AtomicU32::new(0)); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&lsp_request_count); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + let current_call_id = + Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); + let mut new_hints = Vec::with_capacity(2 * current_call_id as usize); + for _ in 0..2 { + let mut i = current_call_id; + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, i), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if i == 0 { + break; + } + i -= 1; + } + } + + Ok(Some(new_hints)) + } + }) + .next() + .await; + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + let mut edits_made = 1; + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("some change", cx); + edits_made += 1; + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string(), "1".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get new hints after an edit" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + edits_made += 1; + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get new hints after hint refresh/ request" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + } + + async fn prepare_test_objects( + cx: &mut TestAppContext, + allowed_hint_kinds: &HashSet>, + ) -> (&'static str, ViewHandle, FakeLanguageServer) { + cx.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.inlay_hints = Some(crate::InlayHintsContent { + enabled: Some(true), + show_type_hints: Some( + allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + ), + show_parameter_hints: Some( + allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + ), + show_other_hints: Some(allowed_hint_kinds.contains(&None)), + }) + }); + }); + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + cx.foreground().start_waiting(); + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + + ("/a/main.rs", editor, fake_server) + } + + #[gpui::test] + async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + let (file_with_hints, editor, fake_server) = + prepare_test_objects(cx, &allowed_hint_kinds).await; + + fake_server + .handle_request::(move |params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![ + lsp::InlayHint { + position: lsp::Position::new(0, 1), + label: lsp::InlayHintLabel::String("type hint".to_string()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 2), + label: lsp::InlayHintLabel::String("parameter hint".to_string()), + kind: Some(lsp::InlayHintKind::PARAMETER), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 3), + label: lsp::InlayHintLabel::String("other hint".to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + ])) + }) + .next() + .await; + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + + let edits_made = 1; + editor.update(cx, |editor, cx| { + assert_eq!( + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string() + ], + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!( + vec!["type hint".to_string(), "other hint".to_string()], + visible_hint_labels(editor, cx) + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + // + } + + pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + crate::init(cx); + }); + + update_test_settings(cx, f); + } + + fn cached_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + labels + } + + fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { + editor + .visible_inlay_hints(cx) + .into_iter() + .map(|hint| hint.text.to_string()) + .collect() + } +} From 3312c9114be4cd75ee1d73459e4ef831e2983d91 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 20:45:29 +0300 Subject: [PATCH 153/169] Improve inlay hint highlights Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/fold_map.rs | 110 +---------------- crates/editor/src/display_map/inlay_map.rs | 134 +++++++++++++++++++-- 2 files changed, 129 insertions(+), 115 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index fe3c0d86abd18661b1be39ec6b1a7bd36b5b5fb0..7d8eae29ab36e44e89d3dd8778cc8fe98f151f2b 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -3,15 +3,13 @@ use super::{ TextHighlights, }; use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset}; -use collections::BTreeMap; use gpui::{color::Color, fonts::HighlightStyle}; use language::{Chunk, Edit, Point, TextSummary}; use std::{ any::TypeId, cmp::{self, Ordering}, - iter::{self, Peekable}, + iter, ops::{Add, AddAssign, Range, Sub}, - vec, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; @@ -656,7 +654,6 @@ impl FoldSnapshot { text_highlights: Option<&'a TextHighlights>, inlay_highlights: Option, ) -> FoldChunks<'a> { - let mut highlight_endpoints = Vec::new(); let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(); let inlay_end = { @@ -671,92 +668,18 @@ impl FoldSnapshot { transform_cursor.start().1 + InlayOffset(overshoot) }; - if let Some(text_highlights) = text_highlights { - if !text_highlights.is_empty() { - while transform_cursor.start().0 < range.end { - if !transform_cursor.item().unwrap().is_fold() { - let transform_start = self.inlay_snapshot.buffer.anchor_after( - self.inlay_snapshot.to_buffer_offset(cmp::max( - inlay_start, - transform_cursor.start().1, - )), - ); - - let transform_end = { - let overshoot = - InlayOffset(range.end.0 - transform_cursor.start().0 .0); - self.inlay_snapshot.buffer.anchor_before( - self.inlay_snapshot.to_buffer_offset(cmp::min( - transform_cursor.end(&()).1, - transform_cursor.start().1 + overshoot, - )), - ) - }; - - for (tag, highlights) in text_highlights.iter() { - let style = highlights.0; - let ranges = &highlights.1; - - let start_ix = match ranges.binary_search_by(|probe| { - let cmp = - probe.end.cmp(&transform_start, &self.inlay_snapshot.buffer); - if cmp.is_gt() { - Ordering::Greater - } else { - Ordering::Less - } - }) { - Ok(i) | Err(i) => i, - }; - for range in &ranges[start_ix..] { - if range - .start - .cmp(&transform_end, &self.inlay_snapshot.buffer) - .is_ge() - { - break; - } - - highlight_endpoints.push(HighlightEndpoint { - offset: self.inlay_snapshot.to_inlay_offset( - range.start.to_offset(&self.inlay_snapshot.buffer), - ), - is_start: true, - tag: *tag, - style, - }); - highlight_endpoints.push(HighlightEndpoint { - offset: self.inlay_snapshot.to_inlay_offset( - range.end.to_offset(&self.inlay_snapshot.buffer), - ), - is_start: false, - tag: *tag, - style, - }); - } - } - } - - transform_cursor.next(&()); - } - highlight_endpoints.sort(); - transform_cursor.seek(&range.start, Bias::Right, &()); - } - } - FoldChunks { transform_cursor, inlay_chunks: self.inlay_snapshot.chunks( inlay_start..inlay_end, language_aware, + text_highlights, inlay_highlights, ), inlay_chunk: None, inlay_offset: inlay_start, output_offset: range.start.0, max_output_offset: range.end.0, - highlight_endpoints: highlight_endpoints.into_iter().peekable(), - active_highlights: Default::default(), ellipses_color: self.ellipses_color, } } @@ -1034,8 +957,6 @@ pub struct FoldChunks<'a> { inlay_offset: InlayOffset, output_offset: usize, max_output_offset: usize, - highlight_endpoints: Peekable>, - active_highlights: BTreeMap, HighlightStyle>, ellipses_color: Option, } @@ -1073,21 +994,6 @@ impl<'a> Iterator for FoldChunks<'a> { }); } - let mut next_highlight_endpoint = InlayOffset(usize::MAX); - while let Some(endpoint) = self.highlight_endpoints.peek().copied() { - if endpoint.offset <= self.inlay_offset { - if endpoint.is_start { - self.active_highlights.insert(endpoint.tag, endpoint.style); - } else { - self.active_highlights.remove(&endpoint.tag); - } - self.highlight_endpoints.next(); - } else { - next_highlight_endpoint = endpoint.offset; - break; - } - } - // Retrieve a chunk from the current location in the buffer. if self.inlay_chunk.is_none() { let chunk_offset = self.inlay_chunks.offset(); @@ -1098,21 +1004,11 @@ impl<'a> Iterator for FoldChunks<'a> { if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk { let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; - let chunk_end = buffer_chunk_end - .min(transform_end) - .min(next_highlight_endpoint); + let chunk_end = buffer_chunk_end.min(transform_end); chunk.text = &chunk.text [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; - if !self.active_highlights.is_empty() { - let mut highlight_style = HighlightStyle::default(); - for active_highlight in self.active_highlights.values() { - highlight_style.highlight(*active_highlight); - } - chunk.highlight_style = Some(highlight_style); - } - if chunk_end == transform_end { self.transform_cursor.next(&()); } else if chunk_end == buffer_chunk_end { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index effd0576ef39b39605bb54520888b0cd4c52ee39..7806c75f17d966f8ca74d5c77d2fa2f04dc15cf0 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -2,16 +2,21 @@ use crate::{ multi_buffer::{MultiBufferChunks, MultiBufferRows}, Anchor, InlayId, MultiBufferSnapshot, ToOffset, }; -use collections::{BTreeSet, HashMap}; +use collections::{BTreeMap, BTreeSet, HashMap}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, Rope, TextSummary}; use std::{ + any::TypeId, cmp, + iter::Peekable, ops::{Add, AddAssign, Range, Sub, SubAssign}, + vec, }; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; +use super::TextHighlights; + pub struct InlayMap { snapshot: InlaySnapshot, inlays_by_id: HashMap, @@ -160,6 +165,28 @@ pub struct InlayBufferRows<'a> { max_buffer_row: u32, } +#[derive(Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: InlayOffset, + is_start: bool, + tag: Option, + style: HighlightStyle, +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| other.is_start.cmp(&self.is_start)) + } +} + pub struct InlayChunks<'a> { transforms: Cursor<'a, Transform, (InlayOffset, usize)>, buffer_chunks: MultiBufferChunks<'a>, @@ -168,6 +195,8 @@ pub struct InlayChunks<'a> { output_offset: InlayOffset, max_output_offset: InlayOffset, highlight_style: Option, + highlight_endpoints: Peekable>, + active_highlights: BTreeMap, HighlightStyle>, snapshot: &'a InlaySnapshot, } @@ -195,6 +224,21 @@ impl<'a> Iterator for InlayChunks<'a> { return None; } + let mut next_highlight_endpoint = InlayOffset(usize::MAX); + while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + if endpoint.offset <= self.output_offset { + if endpoint.is_start { + self.active_highlights.insert(endpoint.tag, endpoint.style); + } else { + self.active_highlights.remove(&endpoint.tag); + } + self.highlight_endpoints.next(); + } else { + next_highlight_endpoint = endpoint.offset; + break; + } + } + let chunk = match self.transforms.item()? { Transform::Isomorphic(_) => { let chunk = self @@ -204,17 +248,28 @@ impl<'a> Iterator for InlayChunks<'a> { *chunk = self.buffer_chunks.next().unwrap(); } - let (prefix, suffix) = chunk.text.split_at(cmp::min( - self.transforms.end(&()).0 .0 - self.output_offset.0, - chunk.text.len(), - )); + let (prefix, suffix) = chunk.text.split_at( + chunk + .text + .len() + .min(self.transforms.end(&()).0 .0 - self.output_offset.0) + .min(next_highlight_endpoint.0 - self.output_offset.0), + ); chunk.text = suffix; self.output_offset.0 += prefix.len(); - Chunk { + let mut prefix = Chunk { text: prefix, ..chunk.clone() + }; + if !self.active_highlights.is_empty() { + let mut highlight_style = HighlightStyle::default(); + for active_highlight in self.active_highlights.values() { + highlight_style.highlight(*active_highlight); + } + prefix.highlight_style = Some(highlight_style); } + prefix } Transform::Inlay(inlay) => { let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| { @@ -871,11 +926,72 @@ impl InlaySnapshot { &'a self, range: Range, language_aware: bool, + text_highlights: Option<&'a TextHighlights>, inlay_highlight_style: Option, ) -> InlayChunks<'a> { let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); cursor.seek(&range.start, Bias::Right, &()); + let mut highlight_endpoints = Vec::new(); + if let Some(text_highlights) = text_highlights { + if !text_highlights.is_empty() { + while cursor.start().0 < range.end { + if true { + let transform_start = self.buffer.anchor_after( + self.to_buffer_offset(cmp::max(range.start, cursor.start().0)), + ); + + let transform_end = { + let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0); + self.buffer.anchor_before(self.to_buffer_offset(cmp::min( + cursor.end(&()).0, + cursor.start().0 + overshoot, + ))) + }; + + for (tag, highlights) in text_highlights.iter() { + let style = highlights.0; + let ranges = &highlights.1; + + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&transform_start, &self.buffer); + if cmp.is_gt() { + cmp::Ordering::Greater + } else { + cmp::Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&transform_end, &self.buffer).is_ge() { + break; + } + + highlight_endpoints.push(HighlightEndpoint { + offset: self + .to_inlay_offset(range.start.to_offset(&self.buffer)), + is_start: true, + tag: *tag, + style, + }); + highlight_endpoints.push(HighlightEndpoint { + offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)), + is_start: false, + tag: *tag, + style, + }); + } + } + } + + cursor.next(&()); + } + highlight_endpoints.sort(); + cursor.seek(&range.start, Bias::Right, &()); + } + } + let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); @@ -887,13 +1003,15 @@ impl InlaySnapshot { output_offset: range.start, max_output_offset: range.end, highlight_style: inlay_highlight_style, + highlight_endpoints: highlight_endpoints.into_iter().peekable(), + active_highlights: Default::default(), snapshot: self, } } #[cfg(test)] pub fn text(&self) -> String { - self.chunks(Default::default()..self.len(), false, None) + self.chunks(Default::default()..self.len(), false, None, None) .map(|chunk| chunk.text) .collect() } @@ -1371,7 +1489,7 @@ mod tests { start = expected_text.clip_offset(start, Bias::Right); let actual_text = inlay_snapshot - .chunks(InlayOffset(start)..InlayOffset(end), false, None) + .chunks(InlayOffset(start)..InlayOffset(end), false, None, None) .map(|chunk| chunk.text) .collect::(); assert_eq!( From 480d8c511bccb331ae7cbae3e1a2554f169bda3c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 21:34:50 +0300 Subject: [PATCH 154/169] Theme hints and suggestions differently --- crates/editor/src/display_map.rs | 18 ++++++--- crates/editor/src/display_map/block_map.rs | 19 ++++++--- crates/editor/src/display_map/fold_map.rs | 12 +++--- crates/editor/src/display_map/inlay_map.rs | 47 +++++++++++++++------- crates/editor/src/display_map/tab_map.rs | 17 +++++--- crates/editor/src/display_map/wrap_map.rs | 19 ++++++--- crates/editor/src/editor.rs | 13 +++--- crates/editor/src/element.rs | 7 +++- crates/editor/src/inlay_hint_cache.rs | 2 +- 9 files changed, 105 insertions(+), 49 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index b117120e81c5921ae9a13bc2da8697742660419b..e62f715cf327de61e0738e7c432ba42c2ed70a3b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -392,7 +392,13 @@ impl DisplaySnapshot { /// Returns text chunks starting at the given display row until the end of the file pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.block_snapshot - .chunks(display_row..self.max_point().row() + 1, false, None, None) + .chunks( + display_row..self.max_point().row() + 1, + false, + None, + None, + None, + ) .map(|h| h.text) } @@ -400,7 +406,7 @@ impl DisplaySnapshot { pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { (0..=display_row).into_iter().rev().flat_map(|row| { self.block_snapshot - .chunks(row..row + 1, false, None, None) + .chunks(row..row + 1, false, None, None, None) .map(|h| h.text) .collect::>() .into_iter() @@ -412,13 +418,15 @@ impl DisplaySnapshot { &self, display_rows: Range, language_aware: bool, - inlay_highlights: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> DisplayChunks<'_> { self.block_snapshot.chunks( display_rows, language_aware, Some(&self.text_highlights), - inlay_highlights, + hint_highlights, + suggestion_highlights, ) } @@ -1711,7 +1719,7 @@ pub mod tests { ) -> Vec<(String, Option, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option, Option)> = Vec::new(); - for chunk in snapshot.chunks(rows, true, None) { + for chunk in snapshot.chunks(rows, true, None, None) { let syntax_color = chunk .syntax_highlight_id .and_then(|id| id.style(theme)?.color); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 8f78c3885a6a288ebbb30fca6b2cbbb931648500..4b76ded3d50cc45d72385d70bbb424b139023f09 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -573,9 +573,15 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(0..self.transforms.summary().output_rows, false, None, None) - .map(|chunk| chunk.text) - .collect() + self.chunks( + 0..self.transforms.summary().output_rows, + false, + None, + None, + None, + ) + .map(|chunk| chunk.text) + .collect() } pub fn chunks<'a>( @@ -583,7 +589,8 @@ impl BlockSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - inlay_highlights: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> BlockChunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); @@ -616,7 +623,8 @@ impl BlockSnapshot { input_start..input_end, language_aware, text_highlights, - inlay_highlights, + hint_highlights, + suggestion_highlights, ), input_chunk: Default::default(), transforms: cursor, @@ -1495,6 +1503,7 @@ mod tests { false, None, None, + None, ) .map(|chunk| chunk.text) .collect::(); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 7d8eae29ab36e44e89d3dd8778cc8fe98f151f2b..01b29e1e1afd04320c5d313a8324e118229da51f 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -475,7 +475,7 @@ pub struct FoldSnapshot { impl FoldSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(FoldOffset(0)..self.len(), false, None, None) + self.chunks(FoldOffset(0)..self.len(), false, None, None, None) .map(|c| c.text) .collect() } @@ -652,7 +652,8 @@ impl FoldSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - inlay_highlights: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> FoldChunks<'a> { let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(); @@ -674,7 +675,8 @@ impl FoldSnapshot { inlay_start..inlay_end, language_aware, text_highlights, - inlay_highlights, + hint_highlights, + suggestion_highlights, ), inlay_chunk: None, inlay_offset: inlay_start, @@ -685,7 +687,7 @@ impl FoldSnapshot { } pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { - self.chunks(start.to_offset(self)..self.len(), false, None, None) + self.chunks(start.to_offset(self)..self.len(), false, None, None, None) .flat_map(|chunk| chunk.text.chars()) } @@ -1514,7 +1516,7 @@ mod tests { let text = &expected_text[start.0..end.0]; assert_eq!( snapshot - .chunks(start..end, false, Some(&highlights), None) + .chunks(start..end, false, Some(&highlights), None, None) .map(|c| c.text) .collect::(), text, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 7806c75f17d966f8ca74d5c77d2fa2f04dc15cf0..1b6884dcc6edec352cec5d545c7711322aab2038 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -194,7 +194,8 @@ pub struct InlayChunks<'a> { inlay_chunks: Option>, output_offset: InlayOffset, max_output_offset: InlayOffset, - highlight_style: Option, + hint_highlight_style: Option, + suggestion_highlight_style: Option, highlight_endpoints: Peekable>, active_highlights: BTreeMap, HighlightStyle>, snapshot: &'a InlaySnapshot, @@ -281,9 +282,13 @@ impl<'a> Iterator for InlayChunks<'a> { let chunk = inlay_chunks.next().unwrap(); self.output_offset.0 += chunk.len(); + let highlight_style = match inlay.id { + InlayId::Suggestion(_) => self.suggestion_highlight_style, + InlayId::Hint(_) => self.hint_highlight_style, + }; Chunk { text: chunk, - highlight_style: self.highlight_style, + highlight_style, ..Default::default() } } @@ -576,7 +581,7 @@ impl InlayMap { let mut to_remove = Vec::new(); let mut to_insert = Vec::new(); let snapshot = &mut self.snapshot; - for _ in 0..rng.gen_range(1..=5) { + for i in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { let position = snapshot.buffer.random_byte_range(0, rng).start; let bias = if rng.gen() { Bias::Left } else { Bias::Right }; @@ -595,8 +600,14 @@ impl InlayMap { bias, text ); + + let inlay_id = if i % 2 == 0 { + InlayId::Hint(post_inc(next_inlay_id)) + } else { + InlayId::Suggestion(post_inc(next_inlay_id)) + }; to_insert.push(( - InlayId(post_inc(next_inlay_id)), + inlay_id, InlayProperties { position: snapshot.buffer.anchor_at(position, bias), text, @@ -927,7 +938,8 @@ impl InlaySnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - inlay_highlight_style: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> InlayChunks<'a> { let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); cursor.seek(&range.start, Bias::Right, &()); @@ -1002,7 +1014,8 @@ impl InlaySnapshot { buffer_chunk: None, output_offset: range.start, max_output_offset: range.end, - highlight_style: inlay_highlight_style, + hint_highlight_style: hint_highlights, + suggestion_highlight_style: suggestion_highlights, highlight_endpoints: highlight_endpoints.into_iter().peekable(), active_highlights: Default::default(), snapshot: self, @@ -1011,7 +1024,7 @@ impl InlaySnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(Default::default()..self.len(), false, None, None) + self.chunks(Default::default()..self.len(), false, None, None, None) .map(|chunk| chunk.text) .collect() } @@ -1078,7 +1091,7 @@ mod tests { let (inlay_snapshot, _) = inlay_map.splice( Vec::new(), vec![( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Hint(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_after(3), text: "|123|", @@ -1157,14 +1170,14 @@ mod tests { Vec::new(), vec![ ( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Hint(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(3), text: "|123|", }, ), ( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Suggestion(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_after(3), text: "|456|", @@ -1370,21 +1383,21 @@ mod tests { Vec::new(), vec![ ( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Hint(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(0), text: "|123|\n", }, ), ( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Hint(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(4), text: "|456|", }, ), ( - InlayId(post_inc(&mut next_inlay_id)), + InlayId::Suggestion(post_inc(&mut next_inlay_id)), InlayProperties { position: buffer.read(cx).snapshot(cx).anchor_before(7), text: "\n|567|\n", @@ -1489,7 +1502,13 @@ mod tests { start = expected_text.clip_offset(start, Bias::Right); let actual_text = inlay_snapshot - .chunks(InlayOffset(start)..InlayOffset(end), false, None, None) + .chunks( + InlayOffset(start)..InlayOffset(end), + false, + None, + None, + None, + ) .map(|chunk| chunk.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index eaaaeed8adb27b6cb9e529e575d616ede4c09028..ca73f6a1a7a7e5bff4d19a32db548c9d2155f744 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -70,6 +70,7 @@ impl TabMap { false, None, None, + None, ) { for (ix, _) in chunk.text.match_indices('\t') { let offset_from_edit = offset_from_edit + (ix as u32); @@ -182,7 +183,7 @@ impl TabSnapshot { self.max_point() }; for c in self - .chunks(range.start..line_end, false, None, None) + .chunks(range.start..line_end, false, None, None, None) .flat_map(|chunk| chunk.text.chars()) { if c == '\n' { @@ -201,6 +202,7 @@ impl TabSnapshot { false, None, None, + None, ) .flat_map(|chunk| chunk.text.chars()) { @@ -222,7 +224,8 @@ impl TabSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - inlay_highlights: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = self.to_fold_point(range.start, Bias::Left); @@ -243,7 +246,8 @@ impl TabSnapshot { input_start..input_end, language_aware, text_highlights, - inlay_highlights, + hint_highlights, + suggestion_highlights, ), input_column, column: expanded_char_column, @@ -266,7 +270,7 @@ impl TabSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(TabPoint::zero()..self.max_point(), false, None, None) + self.chunks(TabPoint::zero()..self.max_point(), false, None, None, None) .map(|chunk| chunk.text) .collect() } @@ -595,6 +599,7 @@ mod tests { false, None, None, + None, ) .map(|c| c.text) .collect::(), @@ -669,7 +674,7 @@ mod tests { let mut chunks = Vec::new(); let mut was_tab = false; let mut text = String::new(); - for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None) { + for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None, None) { if chunk.is_tab != was_tab { if !text.is_empty() { chunks.push((mem::take(&mut text), was_tab)); @@ -738,7 +743,7 @@ mod tests { let expected_summary = TextSummary::from(expected_text.as_str()); assert_eq!( tabs_snapshot - .chunks(start..end, false, None, None) + .chunks(start..end, false, None, None, None) .map(|c| c.text) .collect::(), expected_text, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 0e7f1f816739facc01b1e9af1f00cda4b0ba31ef..fe4723abeea2cffbea6b790d0ea996da6cdf2fe0 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -446,6 +446,7 @@ impl WrapSnapshot { false, None, None, + None, ); let mut edit_transforms = Vec::::new(); for _ in edit.new_rows.start..edit.new_rows.end { @@ -575,7 +576,8 @@ impl WrapSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - inlay_highlights: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> WrapChunks<'a> { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); @@ -593,7 +595,8 @@ impl WrapSnapshot { input_start..input_end, language_aware, text_highlights, - inlay_highlights, + hint_highlights, + suggestion_highlights, ), input_chunk: Default::default(), output_position: output_start, @@ -1324,8 +1327,14 @@ mod tests { } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { - self.chunks(wrap_row..self.max_point().row() + 1, false, None, None) - .map(|h| h.text) + self.chunks( + wrap_row..self.max_point().row() + 1, + false, + None, + None, + None, + ) + .map(|h| h.text) } fn verify_chunks(&mut self, rng: &mut impl Rng) { @@ -1348,7 +1357,7 @@ mod tests { } let actual_text = self - .chunks(start_row..end_row, true, None, None) + .chunks(start_row..end_row, true, None, None, None) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 65beec6d973e1bd8813a6ef840c930f853fb57eb..e96b3d41c3f2cf6c1ccf2311d93c73495e32fb0b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -184,8 +184,11 @@ pub struct GutterHover { pub hovered: bool, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InlayId(usize); +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InlayId { + Suggestion(usize), + Hint(usize), +} actions!( editor, @@ -3449,7 +3452,7 @@ impl Editor { to_remove.push(suggestion.id); } - let suggestion_inlay_id = self.next_inlay_id(); + let suggestion_inlay_id = InlayId::Suggestion(post_inc(&mut self.next_inlay_id)); let to_insert = vec![( suggestion_inlay_id, InlayProperties { @@ -7588,10 +7591,6 @@ impl Editor { cx.write_to_clipboard(ClipboardItem::new(lines)); } - pub fn next_inlay_id(&mut self) -> InlayId { - InlayId(post_inc(&mut self.next_inlay_id)) - } - pub fn inlay_hint_cache(&self) -> &InlayHintCache { &self.inlay_hint_cache } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 928df1027f5624af19aba6dc5bc2936c647da2ed..d1e6f29bbebabf22e0f657a85a16dc6ee8fcc8be 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1392,7 +1392,12 @@ impl EditorElement { } else { let style = &self.style; let chunks = snapshot - .chunks(rows.clone(), true, Some(style.theme.hint)) + .chunks( + rows.clone(), + true, + Some(style.theme.hint), + Some(style.theme.suggestion), + ) .map(|chunk| { let mut highlight_style = chunk .syntax_highlight_id diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 9c686c7980986d487f3efb5efa0ebdc25d1f3a02..d499474a012a494f7447f4a69ed948781325c26f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -616,7 +616,7 @@ async fn fetch_and_update_hints( for new_hint in new_update.add_to_cache { let new_hint_position = multi_buffer_snapshot .anchor_in_excerpt(query.excerpt_id, new_hint.position); - let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id)); + let new_inlay_id = InlayId::Hint(post_inc(&mut editor.next_inlay_id)); if editor .inlay_hint_cache .allowed_hint_kinds From 667b70afde610fc9bd4ce7c7620a5d65fed35c64 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 26 Jun 2023 23:28:56 +0300 Subject: [PATCH 155/169] Move hint settings on the language level --- crates/collab/src/tests/integration_tests.rs | 34 +++-- crates/editor/src/editor.rs | 64 +++++++--- crates/editor/src/editor_settings.rs | 18 --- crates/editor/src/inlay_hint_cache.rs | 128 ++++++++++--------- crates/language/src/language_settings.rs | 64 +++++++++- crates/project/src/lsp_command.rs | 4 +- crates/project/src/project.rs | 25 +--- 7 files changed, 194 insertions(+), 143 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index bc8f7f4353b249d2120fd26e183905e7282f67af..905ce6d2e12d1d951e5e23942395ef81805c0890 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7,8 +7,8 @@ use client::{User, RECEIVE_TIMEOUT}; use collections::HashSet; use editor::{ test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, - ConfirmRename, Editor, EditorSettings, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, - ToggleCodeActions, Undo, + ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, + Undo, }; use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; @@ -18,15 +18,13 @@ use gpui::{ }; use indoc::indoc; use language::{ - language_settings::{AllLanguageSettings, Formatter}, + language_settings::{AllLanguageSettings, Formatter, InlayHintKind, InlayHintSettings}, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, OffsetRangeExt, Point, Rope, }; use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; -use project::{ - search::SearchQuery, DiagnosticSummary, HoverBlockKind, InlayHintKind, Project, ProjectPath, -}; +use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; use rand::prelude::*; use serde_json::json; use settings::SettingsStore; @@ -7823,24 +7821,24 @@ async fn test_mutual_editor_inlay_hint_cache_update( cx_a.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(editor::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some(true), - show_parameter_hints: Some(false), - show_other_hints: Some(true), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, }) }); }); }); cx_b.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(editor::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some(true), - show_parameter_hints: Some(false), - show_other_hints: Some(true), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, }) }); }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e96b3d41c3f2cf6c1ccf2311d93c73495e32fb0b..f70440b92238472bfce7cc367023151ef662dc5d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -31,7 +31,7 @@ use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; -pub use editor_settings::{EditorSettings, InlayHints, InlayHintsContent}; +pub use editor_settings::EditorSettings; pub use element::{ Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, }; @@ -58,7 +58,7 @@ pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ - language_settings::{self, all_language_settings}, + language_settings::{self, all_language_settings, InlayHintSettings}, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, @@ -88,7 +88,7 @@ use std::{ cmp::{self, Ordering, Reverse}, mem, num::NonZeroU32, - ops::{Deref, DerefMut, Range}, + ops::{ControlFlow, Deref, DerefMut, Range}, path::Path, sync::Arc, time::{Duration, Instant}, @@ -1197,7 +1197,7 @@ enum GotoDefinitionKind { #[derive(Debug, Copy, Clone)] enum InlayRefreshReason { - SettingsChange(editor_settings::InlayHints), + SettingsChange(InlayHintSettings), NewLinesShown, ExcerptEdited, RefreshRequested, @@ -1320,6 +1320,12 @@ impl Editor { } } + let inlay_hint_settings = inlay_hint_settings( + selections.newest_anchor().head(), + &buffer.read(cx).snapshot(cx), + cx, + ); + let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1370,7 +1376,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), + inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2607,35 +2613,38 @@ impl Editor { } fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { - if self.project.is_none() - || self.mode != EditorMode::Full - || !settings::get::(cx).inlay_hints.enabled - { + if self.project.is_none() || self.mode != EditorMode::Full { return; } let invalidate_cache = match reason { InlayRefreshReason::SettingsChange(new_settings) => { - let new_splice = self.inlay_hint_cache.update_settings( + match self.inlay_hint_cache.update_settings( &self.buffer, new_settings, self.visible_inlay_hints(cx), cx, - ); - if let Some(InlaySplice { - to_remove, - to_insert, - }) = new_splice - { - self.splice_inlay_hints(to_remove, to_insert, cx); + ) { + ControlFlow::Break(Some(InlaySplice { + to_remove, + to_insert, + })) => { + self.splice_inlay_hints(to_remove, to_insert, cx); + return; + } + ControlFlow::Break(None) => return, + ControlFlow::Continue(()) => InvalidationStrategy::Forced, } - return; } InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict, InlayRefreshReason::RefreshRequested => InvalidationStrategy::Forced, }; + if !self.inlay_hint_cache.enabled { + return; + } + let excerpts_to_query = self .excerpt_visible_offsets(cx) .into_iter() @@ -7298,7 +7307,11 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); self.refresh_inlays( - InlayRefreshReason::SettingsChange(settings::get::(cx).inlay_hints), + InlayRefreshReason::SettingsChange(inlay_hint_settings( + self.selections.newest_anchor().head(), + &self.buffer.read(cx).snapshot(cx), + cx, + )), cx, ); } @@ -7596,6 +7609,19 @@ impl Editor { } } +fn inlay_hint_settings( + location: Anchor, + snapshot: &MultiBufferSnapshot, + cx: &mut ViewContext<'_, '_, Editor>, +) -> InlayHintSettings { + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings + .language(language.map(|l| l.name()).as_deref()) + .inlay_hints +} + fn consume_contiguous_rows( contiguous_row_selections: &mut Vec>, selection: &Selection, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 557c3194c0436c4ede60d623c43fb677a176363c..387d4d2c340d2a3bb5b648d8232880de2d8f7fe1 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -9,7 +9,6 @@ pub struct EditorSettings { pub show_completions_on_input: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, - pub inlay_hints: InlayHints, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -18,14 +17,6 @@ pub struct Scrollbar { pub git_diff: bool, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct InlayHints { - pub enabled: bool, - pub show_type_hints: bool, - pub show_parameter_hints: bool, - pub show_other_hints: bool, -} - #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { @@ -42,7 +33,6 @@ pub struct EditorSettingsContent { pub show_completions_on_input: Option, pub use_on_type_format: Option, pub scrollbar: Option, - pub inlay_hints: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -51,14 +41,6 @@ pub struct ScrollbarContent { pub git_diff: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] -pub struct InlayHintsContent { - pub enabled: Option, - pub show_type_hints: Option, - pub show_parameter_hints: Option, - pub show_other_hints: Option, -} - impl Setting for EditorSettings { const KEY: Option<&'static str> = None; diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index d499474a012a494f7447f4a69ed948781325c26f..1a03886c91a586483c82d85223a2df185b81e0c5 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,24 +1,29 @@ -use std::{cmp, ops::Range, sync::Arc}; +use std::{ + cmp, + ops::{ControlFlow, Range}, + sync::Arc, +}; use crate::{ - display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, - MultiBufferSnapshot, + display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot, }; use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; -use language::{Buffer, BufferSnapshot}; +use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use log::error; use parking_lot::RwLock; -use project::{InlayHint, InlayHintKind}; +use project::InlayHint; use collections::{hash_map, HashMap, HashSet}; +use language::language_settings::InlayHintSettings; use util::post_inc; pub struct InlayHintCache { pub hints: HashMap>>, pub allowed_hint_kinds: HashSet>, pub version: usize, + pub enabled: bool, update_tasks: HashMap, } @@ -138,9 +143,10 @@ struct ExcerptHintsUpdate { } impl InlayHintCache { - pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self { + pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { Self { - allowed_hint_kinds: allowed_hint_types(inlay_hint_settings), + allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), + enabled: inlay_hint_settings.enabled, hints: HashMap::default(), update_tasks: HashMap::default(), version: 0, @@ -150,38 +156,53 @@ impl InlayHintCache { pub fn update_settings( &mut self, multi_buffer: &ModelHandle, - inlay_hint_settings: editor_settings::InlayHints, + new_hint_settings: InlayHintSettings, visible_hints: Vec, cx: &mut ViewContext, - ) -> Option { - let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if !inlay_hint_settings.enabled { - if self.hints.is_empty() { + ) -> ControlFlow> { + dbg!(new_hint_settings); + let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); + match (self.enabled, new_hint_settings.enabled) { + (false, false) => { self.allowed_hint_kinds = new_allowed_hint_kinds; - None - } else { - self.clear(); + ControlFlow::Break(None) + } + (true, true) => { + if new_allowed_hint_kinds == self.allowed_hint_kinds { + ControlFlow::Break(None) + } else { + let new_splice = self.new_allowed_hint_kinds_splice( + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); + if new_splice.is_some() { + self.version += 1; + self.update_tasks.clear(); + self.allowed_hint_kinds = new_allowed_hint_kinds; + } + ControlFlow::Break(new_splice) + } + } + (true, false) => { + self.enabled = new_hint_settings.enabled; self.allowed_hint_kinds = new_allowed_hint_kinds; - Some(InlaySplice { - to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), - to_insert: Vec::new(), - }) + if self.hints.is_empty() { + ControlFlow::Break(None) + } else { + self.clear(); + ControlFlow::Break(Some(InlaySplice { + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), + to_insert: Vec::new(), + })) + } } - } else if new_allowed_hint_kinds == self.allowed_hint_kinds { - None - } else { - let new_splice = self.new_allowed_hint_kinds_splice( - multi_buffer, - &visible_hints, - &new_allowed_hint_kinds, - cx, - ); - if new_splice.is_some() { - self.version += 1; - self.update_tasks.clear(); + (false, true) => { + self.enabled = new_hint_settings.enabled; self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Continue(()) } - new_splice } } @@ -191,6 +212,9 @@ impl InlayHintCache { invalidate: InvalidationStrategy, cx: &mut ViewContext, ) { + if !self.enabled { + return; + } let update_tasks = &mut self.update_tasks; let invalidate_cache = matches!( invalidate, @@ -754,22 +778,6 @@ fn calculate_hint_updates( } } -fn allowed_hint_types( - inlay_hint_settings: editor_settings::InlayHints, -) -> HashSet> { - let mut new_allowed_hint_types = HashSet::default(); - if inlay_hint_settings.show_type_hints { - new_allowed_hint_types.insert(Some(InlayHintKind::Type)); - } - if inlay_hint_settings.show_parameter_hints { - new_allowed_hint_types.insert(Some(InlayHintKind::Parameter)); - } - if inlay_hint_settings.show_other_hints { - new_allowed_hint_types.insert(None); - } - new_allowed_hint_types -} - struct HintFetchRanges { visible_range: Range, other_ranges: Vec>, @@ -788,18 +796,19 @@ fn contains_position( mod tests { use std::sync::atomic::{AtomicU32, Ordering}; - use crate::serde_json::json; + use crate::{serde_json::json, InlayHintSettings}; use futures::StreamExt; use gpui::{TestAppContext, ViewHandle}; use language::{ - language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, + language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, + FakeLspAdapter, Language, LanguageConfig, }; use lsp::FakeLanguageServer; use project::{FakeFs, Project}; use settings::SettingsStore; use workspace::Workspace; - use crate::{editor_tests::update_test_settings, EditorSettings}; + use crate::editor_tests::update_test_settings; use super::*; @@ -926,16 +935,13 @@ mod tests { ) -> (&'static str, ViewHandle, FakeLanguageServer) { cx.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.inlay_hints = Some(crate::InlayHintsContent { - enabled: Some(true), - show_type_hints: Some( - allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - ), - show_parameter_hints: Some( - allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), - ), - show_other_hints: Some(allowed_hint_kinds.contains(&None)), + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), }) }); }); diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 832bb59222fc79dfd00979c0b15306c3d18a3d75..aeceac949341d835b2b2648edd38eef4b366181a 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,6 +1,6 @@ use crate::{File, Language}; use anyhow::Result; -use collections::HashMap; +use collections::{HashMap, HashSet}; use globset::GlobMatcher; use gpui::AppContext; use schemars::{ @@ -52,6 +52,7 @@ pub struct LanguageSettings { pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, pub extend_comment_on_newline: bool, + pub inlay_hints: InlayHintSettings, } #[derive(Clone, Debug, Default)] @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { pub show_whitespaces: Option, #[serde(default)] pub extend_comment_on_newline: Option, + #[serde(default)] + pub inlay_hints: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -150,6 +153,41 @@ pub enum Formatter { }, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHintSettings { + #[serde(default)] + pub enabled: bool, + #[serde(default = "default_true")] + pub show_type_hints: bool, + #[serde(default = "default_true")] + pub show_parameter_hints: bool, + #[serde(default = "default_true")] + pub show_other_hints: bool, +} + +fn default_true() -> bool { + true +} + +impl InlayHintSettings { + pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { + let mut kinds = HashSet::default(); + if !self.enabled { + return kinds; + } + if self.show_type_hints { + kinds.insert(Some(InlayHintKind::Type)); + } + if self.show_parameter_hints { + kinds.insert(Some(InlayHintKind::Parameter)); + } + if self.show_other_hints { + kinds.insert(None); + } + kinds + } +} + impl AllLanguageSettings { pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { if let Some(name) = language_name { @@ -184,6 +222,29 @@ impl AllLanguageSettings { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InlayHintKind { + Type, + Parameter, +} + +impl InlayHintKind { + pub fn from_name(name: &str) -> Option { + match name { + "type" => Some(InlayHintKind::Type), + "parameter" => Some(InlayHintKind::Parameter), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + InlayHintKind::Type => "type", + InlayHintKind::Parameter => "parameter", + } + } +} + impl settings::Setting for AllLanguageSettings { const KEY: Option<&'static str> = None; @@ -347,6 +408,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent &mut settings.extend_comment_on_newline, src.extend_comment_on_newline, ); + merge(&mut settings.inlay_hints, src.inlay_hints); fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 674ee63349ab73e2c571874662a2822a37e3310f..a3c6302e29c51ee46f3143d2374fb473f6a7d6ae 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,5 +1,5 @@ use crate::{ - DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintKind, InlayHintLabel, + DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, MarkupContent, Project, ProjectTransaction, }; @@ -9,7 +9,7 @@ use client::proto::{self, PeerId}; use fs::LineEnding; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ - language_settings::language_settings, + language_settings::{language_settings, InlayHintKind}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a24581a610377f110faac916f7ba8346f51027e0..0896932e7b35ec9df75c9fb5170a69ae10251c0a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,7 +31,7 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter}, + language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -339,29 +339,6 @@ pub struct InlayHint { pub tooltip: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum InlayHintKind { - Type, - Parameter, -} - -impl InlayHintKind { - pub fn from_name(name: &str) -> Option { - match name { - "type" => Some(InlayHintKind::Type), - "parameter" => Some(InlayHintKind::Parameter), - _ => None, - } - } - - pub fn name(&self) -> &'static str { - match self { - InlayHintKind::Type => "type", - InlayHintKind::Parameter => "parameter", - } - } -} - impl InlayHint { pub fn text(&self) -> String { match &self.label { From 15e0feb91da91ba89518c99bcf5af1529999fbc0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 10:15:17 +0300 Subject: [PATCH 156/169] Move highlights from fold to inlay randomized tests --- crates/editor/src/display_map/fold_map.rs | 24 ++-------------------- crates/editor/src/display_map/inlay_map.rs | 24 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 01b29e1e1afd04320c5d313a8324e118229da51f..0b1523fe750326dea5c87f3ec4dcfa350f497185 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1125,8 +1125,7 @@ mod tests { use collections::HashSet; use rand::prelude::*; use settings::SettingsStore; - use std::{cmp::Reverse, env, mem, sync::Arc}; - use sum_tree::TreeMap; + use std::{env, mem}; use text::Patch; use util::test::sample_text; use util::RandomCharIter; @@ -1354,25 +1353,6 @@ mod tests { let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); - let mut highlights = TreeMap::default(); - let highlight_count = rng.gen_range(0_usize..10); - let mut highlight_ranges = (0..highlight_count) - .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) - .collect::>(); - highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); - log::info!("highlighting ranges {:?}", highlight_ranges); - let highlight_ranges = highlight_ranges - .into_iter() - .map(|range| { - buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) - }) - .collect::>(); - - highlights.insert( - Some(TypeId::of::<()>()), - Arc::new((HighlightStyle::default(), highlight_ranges)), - ); - let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); @@ -1516,7 +1496,7 @@ mod tests { let text = &expected_text[start.0..end.0]; assert_eq!( snapshot - .chunks(start..end, false, Some(&highlights), None, None) + .chunks(start..end, false, None, None, None) .map(|c| c.text) .collect::(), text, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 1b6884dcc6edec352cec5d545c7711322aab2038..092dbb80b3300b724748a53a62349c385f57530f 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1076,7 +1076,8 @@ mod tests { use gpui::AppContext; use rand::prelude::*; use settings::SettingsStore; - use std::env; + use std::{cmp::Reverse, env, sync::Arc}; + use sum_tree::TreeMap; use text::Patch; use util::post_inc; @@ -1433,6 +1434,25 @@ mod tests { let mut next_inlay_id = 0; log::info!("buffer text: {:?}", buffer_snapshot.text()); + let mut highlights = TreeMap::default(); + let highlight_count = rng.gen_range(0_usize..10); + let mut highlight_ranges = (0..highlight_count) + .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) + .collect::>(); + highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + log::info!("highlighting ranges {:?}", highlight_ranges); + let highlight_ranges = highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) + }) + .collect::>(); + + highlights.insert( + Some(TypeId::of::<()>()), + Arc::new((HighlightStyle::default(), highlight_ranges)), + ); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); for _ in 0..operations { let mut inlay_edits = Patch::default(); @@ -1505,7 +1525,7 @@ mod tests { .chunks( InlayOffset(start)..InlayOffset(end), false, - None, + Some(&highlights), None, None, ) From 0972766d1dda3d3722367f8f553454ef8731b968 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 00:41:20 +0300 Subject: [PATCH 157/169] Add more hint tests --- crates/collab/src/tests/integration_tests.rs | 226 +++++++++++ crates/editor/src/inlay_hint_cache.rs | 374 +++++++++++++++---- crates/language/src/language_settings.rs | 3 - 3 files changed, 533 insertions(+), 70 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 905ce6d2e12d1d951e5e23942395ef81805c0890..3cf4d9a876094bfd3991e2302d973c57d4944a2e 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -8134,6 +8134,232 @@ async fn test_mutual_editor_inlay_hint_cache_update( }); } +#[gpui::test] +async fn test_inlay_hint_refresh_is_forwarded( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + 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); + let active_call_b = cx_b.read(ActiveCall::global); + + cx_a.update(editor::init); + cx_b.update(editor::init); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: false, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry.add(Arc::clone(&language)); + client_b.language_registry.add(language); + + client_a + .fs + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), 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; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) + .await + .unwrap(); + + let workspace_a = client_a.build_workspace(&project_a, cx_a); + let workspace_b = client_b.build_workspace(&project_b, cx_b); + cx_a.foreground().start_waiting(); + cx_b.foreground().start_waiting(); + + let editor_a = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + let next_call_id = Arc::new(AtomicU32::new(0)); + fake_language_server + .handle_request::(move |params, _| { + let task_next_call_id = Arc::clone(&next_call_id); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst); + let mut new_hints = Vec::with_capacity(current_call_id as usize); + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, current_call_id), + label: lsp::InlayHintLabel::String(current_call_id.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if current_call_id == 0 { + break; + } + current_call_id -= 1; + } + Ok(Some(new_hints)) + } + }) + .next() + .await + .unwrap(); + cx_a.foreground().finish_waiting(); + cx_b.foreground().finish_waiting(); + + cx_a.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert!( + extract_hint_labels(editor).is_empty(), + "Host should get no hints due to them turned off" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Host should have allowed hint kinds set despite hints are off" + ); + assert_eq!( + inlay_cache.version, 0, + "Host should not increment its cache version due to no changes", + ); + }); + + let mut edits_made = 1; + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".to_string()], + extract_hint_labels(editor), + "Client should get its first hints when opens an editor" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest editor update the cache version after every cache/view change" + ); + }); + + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx_a.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert!( + extract_hint_labels(editor).is_empty(), + "Host should get nop hints due to them turned off, even after the /refresh" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 0, + "Host should not increment its cache version due to no changes", + ); + }); + + edits_made += 1; + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".to_string(),], + extract_hint_labels(editor), + "Guest should get a /refresh LSP request propagated by host despite host hints are off" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest should accepted all edits and bump its cache version every time" + ); + }); +} + #[derive(Debug, Eq, PartialEq)] struct RoomParticipants { remote: Vec, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 1a03886c91a586483c82d85223a2df185b81e0c5..43fb7ba7cc72286eec7bd20857f9952e8398b559 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -160,7 +160,6 @@ impl InlayHintCache { visible_hints: Vec, cx: &mut ViewContext, ) -> ControlFlow> { - dbg!(new_hint_settings); let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); match (self.enabled, new_hint_settings.enabled) { (false, false) => { @@ -352,7 +351,6 @@ impl InlayHintCache { self.version += 1; self.update_tasks.clear(); self.hints.clear(); - self.allowed_hint_kinds.clear(); } } @@ -800,8 +798,7 @@ mod tests { use futures::StreamExt; use gpui::{TestAppContext, ViewHandle}; use language::{ - language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, - FakeLspAdapter, Language, LanguageConfig, + language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, }; use lsp::FakeLanguageServer; use project::{FakeFs, Project}; @@ -814,11 +811,16 @@ mod tests { #[gpui::test] async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); - let (file_with_hints, editor, fake_server) = - prepare_test_objects(cx, &allowed_hint_kinds).await; - + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let lsp_request_count = Arc::new(AtomicU32::new(0)); fake_server .handle_request::(move |params, _| { @@ -931,22 +933,7 @@ mod tests { async fn prepare_test_objects( cx: &mut TestAppContext, - allowed_hint_kinds: &HashSet>, ) -> (&'static str, ViewHandle, FakeLanguageServer) { - cx.update(|cx| { - cx.update_global(|store: &mut SettingsStore, cx| { - store.update_user_settings::(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - enabled: true, - show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: allowed_hint_kinds - .contains(&Some(InlayHintKind::Parameter)), - show_other_hints: allowed_hint_kinds.contains(&None), - }) - }); - }); - }); - let mut language = Language::new( LanguageConfig { name: "Rust".into(), @@ -1001,57 +988,73 @@ mod tests { #[gpui::test] async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); - let (file_with_hints, editor, fake_server) = - prepare_test_objects(cx, &allowed_hint_kinds).await; - + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let another_lsp_request_count = Arc::clone(&lsp_request_count); fake_server - .handle_request::(move |params, _| async move { - assert_eq!( - params.text_document.uri, - lsp::Url::from_file_path(file_with_hints).unwrap(), - ); - Ok(Some(vec![ - lsp::InlayHint { - position: lsp::Position::new(0, 1), - label: lsp::InlayHintLabel::String("type hint".to_string()), - kind: Some(lsp::InlayHintKind::TYPE), - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - }, - lsp::InlayHint { - position: lsp::Position::new(0, 2), - label: lsp::InlayHintLabel::String("parameter hint".to_string()), - kind: Some(lsp::InlayHintKind::PARAMETER), - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - }, - lsp::InlayHint { - position: lsp::Position::new(0, 3), - label: lsp::InlayHintLabel::String("other hint".to_string()), - kind: None, - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - }, - ])) + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&another_lsp_request_count); + async move { + Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![ + lsp::InlayHint { + position: lsp::Position::new(0, 1), + label: lsp::InlayHintLabel::String("type hint".to_string()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 2), + label: lsp::InlayHintLabel::String("parameter hint".to_string()), + kind: Some(lsp::InlayHintKind::PARAMETER), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 3), + label: lsp::InlayHintLabel::String("other hint".to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + ])) + } }) .next() .await; cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); - let edits_made = 1; + let mut edits_made = 1; editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 1, + "Should query new hints once" + ); assert_eq!( vec![ "type hint".to_string(), @@ -1076,10 +1079,247 @@ mod tests { ); }); - // + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should load new hints twice" + ); + assert_eq!( + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string() + ], + cached_hint_labels(editor), + "Cached hints should not change due to allowed hint kinds settings update" + ); + assert_eq!( + vec!["type hint".to_string(), "other hint".to_string()], + visible_hint_labels(editor, cx) + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, edits_made, + "Should not update cache version due to new loaded hints being the same" + ); + }); + + for (new_allowed_hint_kinds, expected_visible_hints) in [ + (HashSet::from_iter([None]), vec!["other hint".to_string()]), + ( + HashSet::from_iter([Some(InlayHintKind::Type)]), + vec!["type hint".to_string()], + ), + ( + HashSet::from_iter([Some(InlayHintKind::Parameter)]), + vec!["parameter hint".to_string()], + ), + ( + HashSet::from_iter([None, Some(InlayHintKind::Type)]), + vec!["type hint".to_string(), "other hint".to_string()], + ), + ( + HashSet::from_iter([None, Some(InlayHintKind::Parameter)]), + vec!["parameter hint".to_string(), "other hint".to_string()], + ), + ( + HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]), + vec!["type hint".to_string(), "parameter hint".to_string()], + ), + ( + HashSet::from_iter([ + None, + Some(InlayHintKind::Type), + Some(InlayHintKind::Parameter), + ]), + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string(), + ], + ), + ] { + edits_made += 1; + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: new_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: new_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + expected_visible_hints, + visible_hint_labels(editor, cx), + "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change" + ); + }); + } + + edits_made += 1; + let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: false, + show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: another_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: another_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints when hints got disabled" + ); + assert!( + cached_hint_labels(editor).is_empty(), + "Should clear the cache when hints got disabled" + ); + assert!( + visible_hint_labels(editor, cx).is_empty(), + "Should clear visible hints when hints got disabled" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds, + "Should update its allowed hint kinds even when hints got disabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should update the cache version after hints got disabled" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints when they got disabled" + ); + assert!(cached_hint_labels(editor).is_empty()); + assert!(visible_hint_labels(editor, cx).is_empty()); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should not update the cache version after /refresh query without updates" + ); + }); + + let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); + edits_made += 1; + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: final_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: final_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 3, + "Should query for new hints when they got reenabled" + ); + assert_eq!( + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its cached hints fully repopulated after the hints got reenabled" + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + "Should get its visible hints repopulated and filtered after the h" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds, + "Cache should update editor settings when hints got reenabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Cache should update its version after hints got reenabled" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 4, + "Should query for new hints again" + ); + assert_eq!( + vec![ + "type hint".to_string(), + "parameter hint".to_string(), + "other hint".to_string(), + ], + cached_hint_labels(editor), + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,); + assert_eq!(inlay_cache.version, edits_made); + }); } - pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { cx.foreground().forbid_parking(); cx.update(|cx| { diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index aeceac949341d835b2b2648edd38eef4b366181a..820217567a60b3ce7250014b85fd104967d762a7 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -172,9 +172,6 @@ fn default_true() -> bool { impl InlayHintSettings { pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { let mut kinds = HashSet::default(); - if !self.enabled { - return kinds; - } if self.show_type_hints { kinds.insert(Some(InlayHintKind::Type)); } From 2b59f27c3bb497de936dd757c9711e089848f123 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 11:41:22 +0300 Subject: [PATCH 158/169] Fix fold map tests Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/inlay_map.rs | 41 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 092dbb80b3300b724748a53a62349c385f57530f..affb75f58d25eca1827d229e2106d76eb5cbcb8f 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -707,13 +707,34 @@ impl InlaySnapshot { pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(); cursor.seek(&offset, Bias::Left, &()); - match cursor.item() { - Some(Transform::Isomorphic(_)) => { - let overshoot = offset - cursor.start().0; - InlayOffset(cursor.start().1 .0 + overshoot) + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if offset == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = offset - cursor.start().0; + return InlayOffset(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.len(); + } } - Some(Transform::Inlay(_)) => cursor.start().1, - None => self.len(), } } @@ -1559,6 +1580,14 @@ mod tests { let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point); let mut buffer_chars = buffer_snapshot.chars_at(0); loop { + // Ensure conversion from buffer coordinates to inlay coordinates + // is consistent. + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + assert_eq!( + inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)), + inlay_point + ); + // No matter which bias we clip an inlay point with, it doesn't move // because it was constructed from a buffer point. assert_eq!( From bb9ade5b6fe030bde1b61b2d116d5c7c16541e40 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 12:12:11 +0300 Subject: [PATCH 159/169] Fix wrap map test Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/wrap_map.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index fe4723abeea2cffbea6b790d0ea996da6cdf2fe0..f21c7151ad695b2567dc9cea7da5a5007be1696f 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -760,25 +760,18 @@ impl WrapSnapshot { } let text = language::Rope::from(self.text().as_str()); - let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::>(); + let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0); let mut expected_buffer_rows = Vec::new(); - let mut prev_fold_row = 0; + let mut prev_tab_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let fold_point = self.tab_snapshot.to_fold_point(tab_point, Bias::Left).0; - if fold_point.row() == prev_fold_row && display_row != 0 { + if tab_point.row() == prev_tab_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let inlay_point = fold_point.to_inlay_point(&self.tab_snapshot.fold_snapshot); - let buffer_point = self - .tab_snapshot - .fold_snapshot - .inlay_snapshot - .to_buffer_point(inlay_point); - expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); - prev_fold_row = fold_point.row(); + expected_buffer_rows.push(input_buffer_rows.next().unwrap()); } + prev_tab_row = tab_point.row(); assert_eq!(self.line_len(display_row), text.line_len(display_row)); } From 429a9cddae0bff9a5e12c090cd79f5a4c4cfdb3a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 12:23:58 +0300 Subject: [PATCH 160/169] Use fold points to go to display map's prev/next line boundary Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e62f715cf327de61e0738e7c432ba42c2ed70a3b..714dc74509cbf4ed744a9a9217f18bd52c1c85cb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -321,7 +321,9 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - inlay_point.0.column = 0; + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left); + fold_point.0.column = 0; + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); @@ -337,7 +339,9 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - inlay_point.0.column = self.inlay_snapshot.line_len(inlay_point.row()); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right); + fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); From 30e77aa388c558519cffb0ec4d67d16020ae77ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 27 Jun 2023 14:28:50 +0300 Subject: [PATCH 161/169] More inlay hint cache tests --- crates/editor/src/inlay_hint_cache.rs | 991 +++++++++++++++++++++++--- 1 file changed, 891 insertions(+), 100 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 43fb7ba7cc72286eec7bd20857f9952e8398b559..6fdf9b7d27a14dd9884ddab19edfb07527124728 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -70,20 +70,20 @@ impl ExcerptQuery { if self .dimensions .excerpt_range_start - .cmp(&self.dimensions.excerpt_visible_range_start, buffer) + .cmp(&visible_range.start, buffer) .is_lt() { - let mut end = self.dimensions.excerpt_visible_range_start; + let mut end = visible_range.start; end.offset -= 1; other_ranges.push(self.dimensions.excerpt_range_start..end); } if self .dimensions .excerpt_range_end - .cmp(&self.dimensions.excerpt_visible_range_end, buffer) + .cmp(&visible_range.end, buffer) .is_gt() { - let mut start = self.dimensions.excerpt_visible_range_end; + let mut start = visible_range.end; start.offset += 1; other_ranges.push(start..self.dimensions.excerpt_range_end); } @@ -794,15 +794,19 @@ fn contains_position( mod tests { use std::sync::atomic::{AtomicU32, Ordering}; - use crate::{serde_json::json, InlayHintSettings}; + use crate::{ + scroll::scroll_amount::ScrollAmount, serde_json::json, ExcerptRange, InlayHintSettings, + }; use futures::StreamExt; use gpui::{TestAppContext, ViewHandle}; use language::{ language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, }; use lsp::FakeLanguageServer; + use parking_lot::Mutex; use project::{FakeFs, Project}; use settings::SettingsStore; + use text::Point; use workspace::Workspace; use crate::editor_tests::update_test_settings; @@ -820,6 +824,8 @@ mod tests { show_other_hints: allowed_hint_kinds.contains(&None), }) }); + + cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let lsp_request_count = Arc::new(AtomicU32::new(0)); fake_server @@ -860,6 +866,7 @@ mod tests { .await; cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); + let mut edits_made = 1; editor.update(cx, |editor, cx| { let expected_layers = vec!["0".to_string()]; @@ -931,61 +938,6 @@ mod tests { }); } - async fn prepare_test_objects( - cx: &mut TestAppContext, - ) -> (&'static str, ViewHandle, FakeLanguageServer) { - let mut language = Language::new( - LanguageConfig { - name: "Rust".into(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - Some(tree_sitter_rust::language()), - ); - let mut fake_servers = language - .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { - capabilities: lsp::ServerCapabilities { - inlay_hint_provider: Some(lsp::OneOf::Left(true)), - ..Default::default() - }, - ..Default::default() - })) - .await; - - let fs = FakeFs::new(cx.background()); - fs.insert_tree( - "/a", - json!({ - "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", - "other.rs": "// Test file", - }), - ) - .await; - - let project = Project::test(fs, ["/a".as_ref()], cx).await; - project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let worktree_id = workspace.update(cx, |workspace, cx| { - workspace.project().read_with(cx, |project, cx| { - project.worktrees(cx).next().unwrap().read(cx).id() - }) - }); - - cx.foreground().start_waiting(); - let editor = workspace - .update(cx, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - let fake_server = fake_servers.next().await.unwrap(); - - ("/a/main.rs", editor, fake_server) - } - #[gpui::test] async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); @@ -997,6 +949,8 @@ mod tests { show_other_hints: allowed_hint_kinds.contains(&None), }) }); + + cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let lsp_request_count = Arc::new(AtomicU32::new(0)); let another_lsp_request_count = Arc::clone(&lsp_request_count); @@ -1057,15 +1011,15 @@ mod tests { ); assert_eq!( vec![ - "type hint".to_string(), + "other hint".to_string(), "parameter hint".to_string(), - "other hint".to_string() + "type hint".to_string(), ], cached_hint_labels(editor), "Should get its first hints when opening the editor" ); assert_eq!( - vec!["type hint".to_string(), "other hint".to_string()], + vec!["other hint".to_string(), "type hint".to_string()], visible_hint_labels(editor, cx) ); let inlay_cache = editor.inlay_hint_cache(); @@ -1092,15 +1046,15 @@ mod tests { ); assert_eq!( vec![ - "type hint".to_string(), + "other hint".to_string(), "parameter hint".to_string(), - "other hint".to_string() + "type hint".to_string(), ], cached_hint_labels(editor), "Cached hints should not change due to allowed hint kinds settings update" ); assert_eq!( - vec!["type hint".to_string(), "other hint".to_string()], + vec!["other hint".to_string(), "type hint".to_string()], visible_hint_labels(editor, cx) ); let inlay_cache = editor.inlay_hint_cache(); @@ -1123,15 +1077,15 @@ mod tests { ), ( HashSet::from_iter([None, Some(InlayHintKind::Type)]), - vec!["type hint".to_string(), "other hint".to_string()], + vec!["other hint".to_string(), "type hint".to_string()], ), ( HashSet::from_iter([None, Some(InlayHintKind::Parameter)]), - vec!["parameter hint".to_string(), "other hint".to_string()], + vec!["other hint".to_string(), "parameter hint".to_string()], ), ( HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]), - vec!["type hint".to_string(), "parameter hint".to_string()], + vec!["parameter hint".to_string(), "type hint".to_string()], ), ( HashSet::from_iter([ @@ -1140,9 +1094,9 @@ mod tests { Some(InlayHintKind::Parameter), ]), vec![ - "type hint".to_string(), - "parameter hint".to_string(), "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), ], ), ] { @@ -1165,9 +1119,9 @@ mod tests { ); assert_eq!( vec![ - "type hint".to_string(), - "parameter hint".to_string(), "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), ], cached_hint_labels(editor), "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}" @@ -1267,9 +1221,9 @@ mod tests { ); assert_eq!( vec![ - "type hint".to_string(), - "parameter hint".to_string(), "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), ], cached_hint_labels(editor), "Should get its cached hints fully repopulated after the hints got reenabled" @@ -1303,9 +1257,9 @@ mod tests { ); assert_eq!( vec![ - "type hint".to_string(), - "parameter hint".to_string(), "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), ], cached_hint_labels(editor), ); @@ -1319,41 +1273,878 @@ mod tests { }); } - pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { - cx.foreground().forbid_parking(); + #[gpui::test] + async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); - cx.update(|cx| { - cx.set_global(SettingsStore::test(cx)); - theme::init((), cx); - client::init_settings(cx); - language::init(cx); - Project::init_settings(cx); - workspace::init_settings(cx); - crate::init(cx); + cx.foreground().start_waiting(); + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let fake_server = Arc::new(fake_server); + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let another_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&another_lsp_request_count); + async move { + let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new(0, i), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await; + + let mut expected_changes = Vec::new(); + for change_after_opening in [ + "initial change #1", + "initial change #2", + "initial change #3", + ] { + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(change_after_opening, cx); + }); + expected_changes.push(change_after_opening); + } + + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should query new hints twice: for editor init and for the last edit that interrupted all others" + ); + let expected_hints = vec!["2".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 1, + "Only one update should be registered in the cache after all cancellations" + ); }); - update_test_settings(cx, f); + let mut edits = Vec::new(); + for async_later_change in [ + "another change #1", + "another change #2", + "another change #3", + ] { + expected_changes.push(async_later_change); + let task_editor = editor.clone(); + let mut task_cx = cx.clone(); + edits.push(cx.foreground().spawn(async move { + task_editor.update(&mut task_cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(async_later_change, cx); + }); + })); + } + let _ = futures::future::join_all(edits).await; + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 3, + "Should query new hints one more time, for the last edit only" + ); + let expected_hints = vec!["3".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 2, + "Should update the cache version once more, for the new change" + ); + }); } - fn cached_hint_labels(editor: &Editor) -> Vec { - let mut labels = Vec::new(); - for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { - let excerpt_hints = excerpt_hints.read(); - for (_, inlay) in excerpt_hints.hints.iter() { - match &inlay.label { - project::InlayHintLabel::String(s) => labels.push(s.to_string()), - _ => unreachable!(), + #[gpui::test] + async fn test_hint_refresh_request_cancellation(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + cx.foreground().start_waiting(); + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let fake_server = Arc::new(fake_server); + let mut initial_refresh_tasks = Vec::new(); + let task_cx = cx.clone(); + let add_refresh_task = |tasks: &mut Vec>| { + let task_fake_server = Arc::clone(&fake_server); + tasks.push(task_cx.foreground().spawn(async move { + task_fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + })) + }; + + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let another_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&another_lsp_request_count); + async move { + let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new(0, i), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) } + }) + .next() + .await; + + add_refresh_task(&mut initial_refresh_tasks); + add_refresh_task(&mut initial_refresh_tasks); + let _ = futures::future::join_all(initial_refresh_tasks).await; + + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 3, + "Should query new hints once for editor opening, 2 times for every request" + ); + let expected_hints = vec!["3".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last refresh landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 1, + "Only one update should be registered in the cache after all cancellations" + ); + }); + + let mut expected_changes = Vec::new(); + let mut edits_and_refreshes = Vec::new(); + add_refresh_task(&mut edits_and_refreshes); + for async_later_change in ["change #1", "change #2", "change #3"] { + expected_changes.push(async_later_change); + let task_editor = editor.clone(); + let mut task_cx = cx.clone(); + add_refresh_task(&mut edits_and_refreshes); + edits_and_refreshes.push(cx.foreground().spawn(async move { + task_editor.update(&mut task_cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(async_later_change, cx); + }); + })); + add_refresh_task(&mut edits_and_refreshes); + } + add_refresh_task(&mut edits_and_refreshes); + let _ = futures::future::join_all(edits_and_refreshes).await; + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); } + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 5, + "Should query new hints twice more, for last edit & refresh request after it" + ); + let expected_hints = vec!["5".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit and refresh request only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 2, + "Should update the cache version once since refresh did not get new hint updates" + ); + }); + + let mut edits_and_refreshes = Vec::new(); + add_refresh_task(&mut edits_and_refreshes); + for async_later_change in ["last change #1", "last change #2", "last change #3"] { + expected_changes.push(async_later_change); + let task_editor = editor.clone(); + let mut task_cx = cx.clone(); + add_refresh_task(&mut edits_and_refreshes); + edits_and_refreshes.push(cx.foreground().spawn(async move { + task_editor.update(&mut task_cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(async_later_change, cx); + }); + })); } - labels + let _ = futures::future::join_all(edits_and_refreshes).await; + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 6, + "Should query new hints once more, for last edit. All refresh tasks were before this edit hence should be cancelled." + ); + let expected_hints = vec!["6".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 3, + "Should update the cache version once due to the new change" + ); + }); } - fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { - editor + #[gpui::test] + async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)), + "other.rs": "// Test file", + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + cx.foreground().start_waiting(); + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + let fake_server = fake_servers.next().await.unwrap(); + let lsp_request_ranges = Arc::new(Mutex::new(Vec::new())); + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges); + let closure_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges); + let task_lsp_request_count = Arc::clone(&closure_lsp_request_count); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + + task_lsp_request_ranges.lock().push(params.range); + let query_start = params.range.start; + let query_end = params.range.end; + let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new( + (query_end.line - query_start.line) / 2, + (query_end.character - query_start.character) / 2, + ), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await; + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); + ranges.sort_by_key(|range| range.start); + assert_eq!(ranges.len(), 2, "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints"); + assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document"); + assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line"); + assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent"); + + assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2, + "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints"); + let expected_layers = vec!["1".to_string(), "2".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should have hints from both LSP requests made for a big file" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 2, + "Both LSP queries should've bumped the cache version" + ); + }); + + editor.update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + editor.change_selections(None, cx, |s| s.select_ranges([600..600])); + editor.handle_input("++++more text++++", cx); + }); + + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); + ranges.sort_by_key(|range| range.start); + assert_eq!(ranges.len(), 3, "When scroll is at the middle of a big document, its visible part + 2 other inbisible parts should be queried for hints"); + assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document"); + assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end"); + assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning"); + assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning"); + assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line"); + assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent"); + + assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5, + "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints"); + let expected_layers = vec!["4".to_string(), "5".to_string()]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "Should have hints from the new LSP response after edit"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 4, "Should update the cache for every LSP response with hints added"); + }); + } + + #[gpui::test] + async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let language = Arc::new(language); + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::>().join("")), + "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::>().join("")), + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages().add(Arc::clone(&language)) + }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let buffer_1 = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, "main.rs"), cx) + }) + .await + .unwrap(); + let buffer_2 = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, "other.rs"), cx) + }) + .await + .unwrap(); + let multibuffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ + ExcerptRange { + context: Point::new(0, 0)..Point::new(2, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(4, 0)..Point::new(11, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(22, 0)..Point::new(33, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(44, 0)..Point::new(55, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(56, 0)..Point::new(66, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(67, 0)..Point::new(77, 0), + primary: None, + }, + ], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ + ExcerptRange { + context: Point::new(0, 1)..Point::new(2, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(4, 1)..Point::new(11, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(22, 1)..Point::new(33, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(44, 1)..Point::new(55, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(56, 1)..Point::new(66, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(67, 1)..Point::new(77, 1), + primary: None, + }, + ], + cx, + ); + multibuffer + }); + + cx.foreground().start_waiting(); + let (_, editor) = + cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); + + let fake_server = fake_servers.next().await.unwrap(); + fake_server + .handle_request::(move |params, _| async move { + let hint_text = if params.text_document.uri + == lsp::Url::from_file_path("/a/main.rs").unwrap() + { + "main hint" + } else if params.text_document.uri + == lsp::Url::from_file_path("/a/other.rs").unwrap() + { + "other hint" + } else { + panic!("unexpected uri: {:?}", params.text_document.uri); + }; + + let positions = [ + lsp::Position::new(0, 2), + lsp::Position::new(4, 2), + lsp::Position::new(22, 2), + lsp::Position::new(44, 2), + lsp::Position::new(56, 2), + lsp::Position::new(67, 2), + ]; + let out_of_range_hint = lsp::InlayHint { + position: lsp::Position::new( + params.range.start.line + 99, + params.range.start.character + 99, + ), + label: lsp::InlayHintLabel::String( + "out of excerpt range, should be ignored".to_string(), + ), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }; + Ok(Some( + std::iter::once(out_of_range_hint) + .chain(positions.into_iter().enumerate().map(|(i, position)| { + lsp::InlayHint { + position, + label: lsp::InlayHintLabel::String(format!("{hint_text} #{i}")), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + } + })) + .collect(), + )) + }) + .next() + .await; + + cx.foreground().finish_waiting(); + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + ]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison"); + }); + + editor.update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 11); + }); + + editor.update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was scrolled to the end, further scrolls down should not bring more hints"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 11); + }); + + editor.update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(-0.9), cx); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 11, "No updates should happen during scrolling already scolled buffer"); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([2..2])); + editor.handle_input("++++more text++++", cx); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was edited, hints for the edited buffer (1st) should be requeried for all of its excerpts, \ +unedited (2nd) buffer should have the same hint"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 12); + }); + } + + pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + crate::init(cx); + }); + + update_test_settings(cx, f); + } + + async fn prepare_test_objects( + cx: &mut TestAppContext, + ) -> (&'static str, ViewHandle, FakeLanguageServer) { + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let fake_server = fake_servers.next().await.unwrap(); + + ("/a/main.rs", editor, fake_server) + } + + fn cached_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + + labels.sort(); + labels + } + + fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { + let mut zz = editor .visible_inlay_hints(cx) .into_iter() .map(|hint| hint.text.to_string()) - .collect() + .collect::>(); + zz.sort(); + zz } } From 943c93fda7ed6d719063fe927d40198e21df2c5a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 28 Jun 2023 10:34:51 +0300 Subject: [PATCH 162/169] Simplify hint task queueing --- crates/editor/src/editor.rs | 6 +- crates/editor/src/inlay_hint_cache.rs | 211 ++++++++++---------------- 2 files changed, 87 insertions(+), 130 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f70440b92238472bfce7cc367023151ef662dc5d..5ccfa0470fc19344d49cf11843f017f3bda76653 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2633,12 +2633,12 @@ impl Editor { return; } ControlFlow::Break(None) => return, - ControlFlow::Continue(()) => InvalidationStrategy::Forced, + ControlFlow::Continue(()) => InvalidationStrategy::RefreshRequested, } } InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, - InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict, - InlayRefreshReason::RefreshRequested => InvalidationStrategy::Forced, + InlayRefreshReason::ExcerptEdited => InvalidationStrategy::ExcerptEdited, + InlayRefreshReason::RefreshRequested => InvalidationStrategy::RefreshRequested, }; if !self.inlay_hint_cache.enabled { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 6fdf9b7d27a14dd9884ddab19edfb07527124728..580308f6a60d1f1064a51302720799796b325637 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -9,6 +9,7 @@ use crate::{ }; use anyhow::Context; use clock::Global; +use futures::{future::Shared, FutureExt}; use gpui::{ModelHandle, Task, ViewContext}; use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use log::error; @@ -28,14 +29,10 @@ pub struct InlayHintCache { } struct UpdateTask { - current: (InvalidationStrategy, SpawnedTask), - pending_refresh: Option, -} - -struct SpawnedTask { - version: usize, - is_running_rx: smol::channel::Receiver<()>, - _task: Task<()>, + invalidation_strategy: InvalidationStrategy, + cache_version: usize, + _task: Shared>, + pending_refresh: Option>, } #[derive(Debug)] @@ -95,35 +92,10 @@ impl ExcerptQuery { } } -impl UpdateTask { - fn new(invalidation_strategy: InvalidationStrategy, spawned_task: SpawnedTask) -> Self { - Self { - current: (invalidation_strategy, spawned_task), - pending_refresh: None, - } - } - - fn is_running(&self) -> bool { - !self.current.1.is_running_rx.is_closed() - || self - .pending_refresh - .as_ref() - .map_or(false, |task| !task.is_running_rx.is_closed()) - } - - fn cache_version(&self) -> usize { - self.current.1.version - } - - fn invalidation_strategy(&self) -> InvalidationStrategy { - self.current.0 - } -} - #[derive(Debug, Clone, Copy)] pub enum InvalidationStrategy { - Forced, - OnConflict, + RefreshRequested, + ExcerptEdited, None, } @@ -217,7 +189,7 @@ impl InlayHintCache { let update_tasks = &mut self.update_tasks; let invalidate_cache = matches!( invalidate, - InvalidationStrategy::Forced | InvalidationStrategy::OnConflict + InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited ); if invalidate_cache { update_tasks @@ -226,7 +198,7 @@ impl InlayHintCache { let cache_version = self.version; excerpts_to_query.retain(|visible_excerpt_id, _| { match update_tasks.entry(*visible_excerpt_id) { - hash_map::Entry::Occupied(o) => match o.get().cache_version().cmp(&cache_version) { + hash_map::Entry::Occupied(o) => match o.get().cache_version.cmp(&cache_version) { cmp::Ordering::Less => true, cmp::Ordering::Equal => invalidate_cache, cmp::Ordering::Greater => false, @@ -367,25 +339,23 @@ fn spawn_new_update_tasks( let buffer = buffer_handle.read(cx); let buffer_snapshot = buffer.snapshot(); let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); - let cache_is_empty = match &cached_excerpt_hints { - Some(cached_excerpt_hints) => { - let new_task_buffer_version = buffer_snapshot.version(); - let cached_excerpt_hints = cached_excerpt_hints.read(); - let cached_buffer_version = &cached_excerpt_hints.buffer_version; - if cached_excerpt_hints.version > update_cache_version - || cached_buffer_version.changed_since(new_task_buffer_version) - { - return; - } - if !new_task_buffer_version.changed_since(&cached_buffer_version) - && !matches!(invalidation_strategy, InvalidationStrategy::Forced) - { - return; - } - - cached_excerpt_hints.hints.is_empty() + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let new_task_buffer_version = buffer_snapshot.version(); + let cached_excerpt_hints = cached_excerpt_hints.read(); + let cached_buffer_version = &cached_excerpt_hints.buffer_version; + if cached_excerpt_hints.version > update_cache_version + || cached_buffer_version.changed_since(new_task_buffer_version) + { + return; + } + if !new_task_buffer_version.changed_since(&cached_buffer_version) + && !matches!( + invalidation_strategy, + InvalidationStrategy::RefreshRequested + ) + { + return; } - None => true, }; let buffer_id = buffer.remote_id(); @@ -419,55 +389,53 @@ fn spawn_new_update_tasks( invalidate: invalidation_strategy, }; - let new_update_task = |previous_task| { + let new_update_task = |is_refresh_after_regular_task| { new_update_task( query, multi_buffer_snapshot, buffer_snapshot, Arc::clone(&visible_hints), cached_excerpt_hints, - previous_task, + is_refresh_after_regular_task, cx, ) }; match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { hash_map::Entry::Occupied(mut o) => { let update_task = o.get_mut(); - if update_task.is_running() { - match (update_task.invalidation_strategy(), invalidation_strategy) { - (InvalidationStrategy::Forced, _) - | (_, InvalidationStrategy::OnConflict) => { - o.insert(UpdateTask::new( - invalidation_strategy, - new_update_task(None), - )); - } - (_, InvalidationStrategy::Forced) => { - if cache_is_empty { - o.insert(UpdateTask::new( - invalidation_strategy, - new_update_task(None), - )); - } else if update_task.pending_refresh.is_none() { - update_task.pending_refresh = Some(new_update_task(Some( - update_task.current.1.is_running_rx.clone(), - ))); - } - } - _ => {} + match (update_task.invalidation_strategy, invalidation_strategy) { + (_, InvalidationStrategy::None) => {} + (InvalidationStrategy::RefreshRequested, _) + | (_, InvalidationStrategy::ExcerptEdited) + | ( + InvalidationStrategy::None, + InvalidationStrategy::RefreshRequested, + ) => { + o.insert(UpdateTask { + invalidation_strategy, + cache_version: query.cache_version, + _task: new_update_task(false).shared(), + pending_refresh: None, + }); + } + (_, InvalidationStrategy::RefreshRequested) => { + let pending_fetch = o.get()._task.clone(); + let refresh_task = new_update_task(true); + o.get_mut().pending_refresh = + Some(cx.background().spawn(async move { + pending_fetch.await; + refresh_task.await + })); } - } else { - o.insert(UpdateTask::new( - invalidation_strategy, - new_update_task(None), - )); } } hash_map::Entry::Vacant(v) => { - v.insert(UpdateTask::new( + v.insert(UpdateTask { invalidation_strategy, - new_update_task(None), - )); + cache_version: query.cache_version, + _task: new_update_task(false).shared(), + pending_refresh: None, + }); } } } @@ -481,17 +449,11 @@ fn new_update_task( buffer_snapshot: BufferSnapshot, visible_hints: Arc>, cached_excerpt_hints: Option>>, - task_before_refresh: Option>, + is_refresh_after_regular_task: bool, cx: &mut ViewContext<'_, '_, Editor>, -) -> SpawnedTask { +) -> Task<()> { let hints_fetch_tasks = query.hints_fetch_ranges(&buffer_snapshot); - let (is_running_tx, is_running_rx) = smol::channel::bounded(1); - let is_refresh_task = task_before_refresh.is_some(); - let _task = cx.spawn(|editor, cx| async move { - let _is_running_tx = is_running_tx; - if let Some(task_before_refresh) = task_before_refresh { - task_before_refresh.recv().await.ok(); - } + cx.spawn(|editor, cx| async move { let create_update_task = |range| { fetch_and_update_hints( editor.clone(), @@ -505,7 +467,7 @@ fn new_update_task( ) }; - if is_refresh_task { + if is_refresh_after_regular_task { let visible_range_has_updates = match create_update_task(hints_fetch_tasks.visible_range).await { Ok(updated) => updated, @@ -545,13 +507,7 @@ fn new_update_task( } } } - }); - - SpawnedTask { - version: query.cache_version, - _task, - is_running_rx, - } + }) } async fn fetch_and_update_hints( @@ -716,7 +672,7 @@ fn calculate_hint_updates( let mut remove_from_cache = HashSet::default(); if matches!( query.invalidate, - InvalidationStrategy::Forced | InvalidationStrategy::OnConflict + InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited ) { remove_from_visible.extend( visible_hints @@ -1421,18 +1377,6 @@ mod tests { cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let fake_server = Arc::new(fake_server); - let mut initial_refresh_tasks = Vec::new(); - let task_cx = cx.clone(); - let add_refresh_task = |tasks: &mut Vec>| { - let task_fake_server = Arc::clone(&fake_server); - tasks.push(task_cx.foreground().spawn(async move { - task_fake_server - .request::(()) - .await - .expect("inlay refresh request failed"); - })) - }; - let lsp_request_count = Arc::new(AtomicU32::new(0)); let another_lsp_request_count = Arc::clone(&lsp_request_count); fake_server @@ -1459,6 +1403,17 @@ mod tests { .next() .await; + let mut initial_refresh_tasks = Vec::new(); + let task_cx = cx.clone(); + let add_refresh_task = |tasks: &mut Vec>| { + let task_fake_server = Arc::clone(&fake_server); + tasks.push(task_cx.foreground().spawn(async move { + task_fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + })) + }; add_refresh_task(&mut initial_refresh_tasks); add_refresh_task(&mut initial_refresh_tasks); let _ = futures::future::join_all(initial_refresh_tasks).await; @@ -1470,7 +1425,7 @@ mod tests { assert_eq!( lsp_request_count.load(Ordering::Relaxed), 3, - "Should query new hints once for editor opening, 2 times for every request" + "Should query new hints once for editor opening and 2 times due to 2 refresh requests" ); let expected_hints = vec!["3".to_string()]; assert_eq!( @@ -1494,16 +1449,22 @@ mod tests { expected_changes.push(async_later_change); let task_editor = editor.clone(); let mut task_cx = cx.clone(); - add_refresh_task(&mut edits_and_refreshes); + let task_fake_server = Arc::clone(&fake_server); edits_and_refreshes.push(cx.foreground().spawn(async move { + task_fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); task_editor.update(&mut task_cx, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(async_later_change, cx); }); + task_fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); })); - add_refresh_task(&mut edits_and_refreshes); } - add_refresh_task(&mut edits_and_refreshes); let _ = futures::future::join_all(edits_and_refreshes).await; cx.foreground().run_until_parked(); @@ -1515,12 +1476,8 @@ mod tests { "Should apply all changes made" ); } - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 5, - "Should query new hints twice more, for last edit & refresh request after it" - ); - let expected_hints = vec!["5".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 13); + let expected_hints = vec!["13".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), From 083e4e76e20d307ed42ab338c99eb46ac0cb1f7f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 28 Jun 2023 11:42:14 +0300 Subject: [PATCH 163/169] Better tests, invalidate multibuffer excerpts better --- crates/editor/src/inlay_hint_cache.rs | 206 ++++++++++++++++---------- 1 file changed, 127 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 580308f6a60d1f1064a51302720799796b325637..f132a17673c68b840934bab9b83380e1662bdede 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -39,6 +39,7 @@ struct UpdateTask { pub struct CachedExcerptHints { version: usize, buffer_version: Global, + buffer_id: u64, pub hints: Vec<(InlayId, InlayHint)>, } @@ -60,6 +61,13 @@ struct ExcerptDimensions { } impl ExcerptQuery { + fn should_invalidate(&self) -> bool { + matches!( + self.invalidate, + InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited + ) + } + fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges { let visible_range = self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end; @@ -570,6 +578,7 @@ async fn fetch_and_update_hints( Arc::new(RwLock::new(CachedExcerptHints { version: new_update.cache_version, buffer_version: buffer_snapshot.version().clone(), + buffer_id: query.buffer_id, hints: Vec::new(), })) }); @@ -615,6 +624,28 @@ async fn fetch_and_update_hints( }); drop(cached_excerpt_hints); + if query.should_invalidate() { + let mut outdated_excerpt_caches = HashSet::default(); + for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() { + let excerpt_hints = excerpt_hints.read(); + if excerpt_hints.buffer_id == query.buffer_id + && excerpt_id != &query.excerpt_id + && buffer_snapshot + .version() + .changed_since(&excerpt_hints.buffer_version) + { + outdated_excerpt_caches.insert(*excerpt_id); + splice + .to_remove + .extend(excerpt_hints.hints.iter().map(|(id, _)| id)); + } + } + editor + .inlay_hint_cache + .hints + .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id)); + } + let InlaySplice { to_remove, to_insert, @@ -670,10 +701,7 @@ fn calculate_hint_updates( let mut remove_from_visible = Vec::new(); let mut remove_from_cache = HashSet::default(); - if matches!( - query.invalidate, - InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited - ) { + if query.should_invalidate() { remove_from_visible.extend( visible_hints .iter() @@ -748,10 +776,12 @@ fn contains_position( #[cfg(test)] mod tests { - use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use crate::{ - scroll::scroll_amount::ScrollAmount, serde_json::json, ExcerptRange, InlayHintSettings, + scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, + serde_json::json, + ExcerptRange, InlayHintSettings, }; use futures::StreamExt; use gpui::{TestAppContext, ViewHandle}; @@ -1820,60 +1850,70 @@ mod tests { let (_, editor) = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); + let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); + let closure_editor_edited = Arc::clone(&editor_edited); fake_server - .handle_request::(move |params, _| async move { - let hint_text = if params.text_document.uri - == lsp::Url::from_file_path("/a/main.rs").unwrap() - { - "main hint" - } else if params.text_document.uri - == lsp::Url::from_file_path("/a/other.rs").unwrap() - { - "other hint" - } else { - panic!("unexpected uri: {:?}", params.text_document.uri); - }; - - let positions = [ - lsp::Position::new(0, 2), - lsp::Position::new(4, 2), - lsp::Position::new(22, 2), - lsp::Position::new(44, 2), - lsp::Position::new(56, 2), - lsp::Position::new(67, 2), - ]; - let out_of_range_hint = lsp::InlayHint { - position: lsp::Position::new( - params.range.start.line + 99, - params.range.start.character + 99, - ), - label: lsp::InlayHintLabel::String( - "out of excerpt range, should be ignored".to_string(), - ), - kind: None, - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - }; - Ok(Some( - std::iter::once(out_of_range_hint) - .chain(positions.into_iter().enumerate().map(|(i, position)| { - lsp::InlayHint { - position, - label: lsp::InlayHintLabel::String(format!("{hint_text} #{i}")), - kind: None, - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - } - })) - .collect(), - )) + .handle_request::(move |params, _| { + let task_editor_edited = Arc::clone(&closure_editor_edited); + async move { + let hint_text = if params.text_document.uri + == lsp::Url::from_file_path("/a/main.rs").unwrap() + { + "main hint" + } else if params.text_document.uri + == lsp::Url::from_file_path("/a/other.rs").unwrap() + { + "other hint" + } else { + panic!("unexpected uri: {:?}", params.text_document.uri); + }; + + let positions = [ + lsp::Position::new(0, 2), + lsp::Position::new(4, 2), + lsp::Position::new(22, 2), + lsp::Position::new(44, 2), + lsp::Position::new(56, 2), + lsp::Position::new(67, 2), + ]; + let out_of_range_hint = lsp::InlayHint { + position: lsp::Position::new( + params.range.start.line + 99, + params.range.start.character + 99, + ), + label: lsp::InlayHintLabel::String( + "out of excerpt range, should be ignored".to_string(), + ), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }; + + let edited = task_editor_edited.load(Ordering::Acquire); + Ok(Some( + std::iter::once(out_of_range_hint) + .chain(positions.into_iter().enumerate().map(|(i, position)| { + lsp::InlayHint { + position, + label: lsp::InlayHintLabel::String(format!( + "{hint_text}{} #{i}", + if edited { "(edited)" } else { "" }, + )), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + } + })) + .collect(), + )) + } }) .next() .await; @@ -1900,9 +1940,15 @@ mod tests { }); editor.update(cx, |editor, cx| { - editor.scroll_screen(&ScrollAmount::Page(0.9), cx); - editor.scroll_screen(&ScrollAmount::Page(0.9), cx); - editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(56, 0)..Point::new(56, 0)]) + }); }); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1911,24 +1957,24 @@ mod tests { "main hint #1".to_string(), "main hint #2".to_string(), "main hint #3".to_string(), + "main hint #4".to_string(), "main hint #5".to_string(), "other hint #0".to_string(), "other hint #1".to_string(), "other hint #2".to_string(), - "other hint #3".to_string(), - "other hint #4".to_string(), - "other hint #5".to_string(), ]; assert_eq!(expected_layers, cached_hint_labels(editor), "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); assert_eq!(expected_layers, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 11); + assert_eq!(inlay_cache.version, 9); }); editor.update(cx, |editor, cx| { - editor.scroll_screen(&ScrollAmount::Page(0.9), cx); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) + }); }); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1937,6 +1983,7 @@ mod tests { "main hint #1".to_string(), "main hint #2".to_string(), "main hint #3".to_string(), + "main hint #4".to_string(), "main hint #5".to_string(), "other hint #0".to_string(), "other hint #1".to_string(), @@ -1946,15 +1993,17 @@ mod tests { "other hint #5".to_string(), ]; assert_eq!(expected_layers, cached_hint_labels(editor), - "After multibuffer was scrolled to the end, further scrolls down should not bring more hints"); + "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched"); assert_eq!(expected_layers, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 11); + assert_eq!(inlay_cache.version, 12); }); editor.update(cx, |editor, cx| { - editor.scroll_screen(&ScrollAmount::Page(-0.9), cx); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); }); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1963,6 +2012,7 @@ mod tests { "main hint #1".to_string(), "main hint #2".to_string(), "main hint #3".to_string(), + "main hint #4".to_string(), "main hint #5".to_string(), "other hint #0".to_string(), "other hint #1".to_string(), @@ -1976,22 +2026,20 @@ mod tests { assert_eq!(expected_layers, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 11, "No updates should happen during scrolling already scolled buffer"); + assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer"); }); + editor_edited.store(true, Ordering::Release); editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([2..2])); editor.handle_input("++++more text++++", cx); }); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { let expected_layers = vec![ - "main hint #0".to_string(), - "main hint #0".to_string(), - "main hint #1".to_string(), - "main hint #2".to_string(), - "main hint #3".to_string(), - "main hint #5".to_string(), + "main hint(edited) #0".to_string(), + "main hint(edited) #1".to_string(), + "main hint(edited) #2".to_string(), + "main hint(edited) #3".to_string(), "other hint #0".to_string(), "other hint #1".to_string(), "other hint #2".to_string(), @@ -2000,12 +2048,12 @@ mod tests { "other hint #5".to_string(), ]; assert_eq!(expected_layers, cached_hint_labels(editor), - "After multibuffer was edited, hints for the edited buffer (1st) should be requeried for all of its excerpts, \ + "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \ unedited (2nd) buffer should have the same hint"); assert_eq!(expected_layers, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 12); + assert_eq!(inlay_cache.version, 16); }); } From 98edc0f88519924a5c0f35f2723297412ea8f2f9 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 28 Jun 2023 12:18:02 +0300 Subject: [PATCH 164/169] Simplify the hint cache code --- crates/editor/src/editor.rs | 32 ++++---- crates/editor/src/inlay_hint_cache.rs | 110 ++++++++++++-------------- 2 files changed, 65 insertions(+), 77 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5ccfa0470fc19344d49cf11843f017f3bda76653..64332c102aa8a802bb6e428250b820597060590c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2641,22 +2641,11 @@ impl Editor { InlayRefreshReason::RefreshRequested => InvalidationStrategy::RefreshRequested, }; - if !self.inlay_hint_cache.enabled { - return; - } - - let excerpts_to_query = self - .excerpt_visible_offsets(cx) - .into_iter() - .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) - .map(|(buffer, excerpt_visible_range, excerpt_id)| { - (excerpt_id, (buffer, excerpt_visible_range)) - }) - .collect::>(); - if !excerpts_to_query.is_empty() { - self.inlay_hint_cache - .refresh_inlay_hints(excerpts_to_query, invalidate_cache, cx) - } + self.inlay_hint_cache.refresh_inlay_hints( + self.excerpt_visible_offsets(cx), + invalidate_cache, + cx, + ) } fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec { @@ -2673,7 +2662,7 @@ impl Editor { fn excerpt_visible_offsets( &self, cx: &mut ViewContext<'_, '_, Editor>, - ) -> Vec<(ModelHandle, Range, ExcerptId)> { + ) -> HashMap, Range)> { let multi_buffer = self.buffer().read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); let multi_buffer_visible_start = self @@ -2687,7 +2676,14 @@ impl Editor { Bias::Left, ); let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; - multi_buffer.range_to_buffer_ranges(multi_buffer_visible_range, cx) + multi_buffer + .range_to_buffer_ranges(multi_buffer_visible_range, cx) + .into_iter() + .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) + .map(|(buffer, excerpt_visible_range, excerpt_id)| { + (excerpt_id, (buffer, excerpt_visible_range)) + }) + .collect() } fn splice_inlay_hints( diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index f132a17673c68b840934bab9b83380e1662bdede..e6f5fe03d10b7df05ecab8a2d98272a04eec1511 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -28,6 +28,27 @@ pub struct InlayHintCache { update_tasks: HashMap, } +#[derive(Debug)] +pub struct CachedExcerptHints { + version: usize, + buffer_version: Global, + buffer_id: u64, + pub hints: Vec<(InlayId, InlayHint)>, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidationStrategy { + RefreshRequested, + ExcerptEdited, + None, +} + +#[derive(Debug, Default)] +pub struct InlaySplice { + pub to_remove: Vec, + pub to_insert: Vec<(Anchor, InlayId, InlayHint)>, +} + struct UpdateTask { invalidation_strategy: InvalidationStrategy, cache_version: usize, @@ -36,11 +57,11 @@ struct UpdateTask { } #[derive(Debug)] -pub struct CachedExcerptHints { - version: usize, - buffer_version: Global, - buffer_id: u64, - pub hints: Vec<(InlayId, InlayHint)>, +struct ExcerptHintsUpdate { + excerpt_id: ExcerptId, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: HashSet, } #[derive(Debug, Clone, Copy)] @@ -60,14 +81,16 @@ struct ExcerptDimensions { excerpt_visible_range_end: language::Anchor, } -impl ExcerptQuery { +impl InvalidationStrategy { fn should_invalidate(&self) -> bool { matches!( - self.invalidate, + self, InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited ) } +} +impl ExcerptQuery { fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges { let visible_range = self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end; @@ -100,28 +123,6 @@ impl ExcerptQuery { } } -#[derive(Debug, Clone, Copy)] -pub enum InvalidationStrategy { - RefreshRequested, - ExcerptEdited, - None, -} - -#[derive(Debug, Default)] -pub struct InlaySplice { - pub to_remove: Vec, - pub to_insert: Vec<(Anchor, InlayId, InlayHint)>, -} - -#[derive(Debug)] -struct ExcerptHintsUpdate { - excerpt_id: ExcerptId, - cache_version: usize, - remove_from_visible: Vec, - remove_from_cache: HashSet, - add_to_cache: HashSet, -} - impl InlayHintCache { pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { Self { @@ -191,15 +192,11 @@ impl InlayHintCache { invalidate: InvalidationStrategy, cx: &mut ViewContext, ) { - if !self.enabled { + if !self.enabled || excerpts_to_query.is_empty() { return; } let update_tasks = &mut self.update_tasks; - let invalidate_cache = matches!( - invalidate, - InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited - ); - if invalidate_cache { + if invalidate.should_invalidate() { update_tasks .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); } @@ -208,7 +205,7 @@ impl InlayHintCache { match update_tasks.entry(*visible_excerpt_id) { hash_map::Entry::Occupied(o) => match o.get().cache_version.cmp(&cache_version) { cmp::Ordering::Less => true, - cmp::Ordering::Equal => invalidate_cache, + cmp::Ordering::Equal => invalidate.should_invalidate(), cmp::Ordering::Greater => false, }, hash_map::Entry::Vacant(_) => true, @@ -337,7 +334,7 @@ impl InlayHintCache { fn spawn_new_update_tasks( editor: &mut Editor, excerpts_to_query: HashMap, Range)>, - invalidation_strategy: InvalidationStrategy, + invalidate: InvalidationStrategy, update_cache_version: usize, cx: &mut ViewContext<'_, '_, Editor>, ) { @@ -357,10 +354,7 @@ fn spawn_new_update_tasks( return; } if !new_task_buffer_version.changed_since(&cached_buffer_version) - && !matches!( - invalidation_strategy, - InvalidationStrategy::RefreshRequested - ) + && !matches!(invalidate, InvalidationStrategy::RefreshRequested) { return; } @@ -394,7 +388,7 @@ fn spawn_new_update_tasks( excerpt_visible_range_end, }, cache_version: update_cache_version, - invalidate: invalidation_strategy, + invalidate, }; let new_update_task = |is_refresh_after_regular_task| { @@ -411,7 +405,7 @@ fn spawn_new_update_tasks( match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { hash_map::Entry::Occupied(mut o) => { let update_task = o.get_mut(); - match (update_task.invalidation_strategy, invalidation_strategy) { + match (update_task.invalidation_strategy, invalidate) { (_, InvalidationStrategy::None) => {} (InvalidationStrategy::RefreshRequested, _) | (_, InvalidationStrategy::ExcerptEdited) @@ -420,7 +414,7 @@ fn spawn_new_update_tasks( InvalidationStrategy::RefreshRequested, ) => { o.insert(UpdateTask { - invalidation_strategy, + invalidation_strategy: invalidate, cache_version: query.cache_version, _task: new_update_task(false).shared(), pending_refresh: None, @@ -439,7 +433,7 @@ fn spawn_new_update_tasks( } hash_map::Entry::Vacant(v) => { v.insert(UpdateTask { - invalidation_strategy, + invalidation_strategy: invalidate, cache_version: query.cache_version, _task: new_update_task(false).shared(), pending_refresh: None, @@ -460,7 +454,7 @@ fn new_update_task( is_refresh_after_regular_task: bool, cx: &mut ViewContext<'_, '_, Editor>, ) -> Task<()> { - let hints_fetch_tasks = query.hints_fetch_ranges(&buffer_snapshot); + let hints_fetch_ranges = query.hints_fetch_ranges(&buffer_snapshot); cx.spawn(|editor, cx| async move { let create_update_task = |range| { fetch_and_update_hints( @@ -477,7 +471,7 @@ fn new_update_task( if is_refresh_after_regular_task { let visible_range_has_updates = - match create_update_task(hints_fetch_tasks.visible_range).await { + match create_update_task(hints_fetch_ranges.visible_range).await { Ok(updated) => updated, Err(e) => { error!("inlay hint visible range update task failed: {e:#}"); @@ -487,7 +481,7 @@ fn new_update_task( if visible_range_has_updates { let other_update_results = futures::future::join_all( - hints_fetch_tasks + hints_fetch_ranges .other_ranges .into_iter() .map(create_update_task), @@ -497,14 +491,13 @@ fn new_update_task( for result in other_update_results { if let Err(e) = result { error!("inlay hint update task failed: {e:#}"); - return; } } } } else { let task_update_results = futures::future::join_all( - std::iter::once(hints_fetch_tasks.visible_range) - .chain(hints_fetch_tasks.other_ranges.into_iter()) + std::iter::once(hints_fetch_ranges.visible_range) + .chain(hints_fetch_ranges.other_ranges.into_iter()) .map(create_update_task), ) .await; @@ -576,17 +569,17 @@ async fn fetch_and_update_hints( .entry(new_update.excerpt_id) .or_insert_with(|| { Arc::new(RwLock::new(CachedExcerptHints { - version: new_update.cache_version, + version: query.cache_version, buffer_version: buffer_snapshot.version().clone(), buffer_id: query.buffer_id, hints: Vec::new(), })) }); let mut cached_excerpt_hints = cached_excerpt_hints.write(); - match new_update.cache_version.cmp(&cached_excerpt_hints.version) { + match query.cache_version.cmp(&cached_excerpt_hints.version) { cmp::Ordering::Less => return, cmp::Ordering::Greater | cmp::Ordering::Equal => { - cached_excerpt_hints.version = new_update.cache_version; + cached_excerpt_hints.version = query.cache_version; } } cached_excerpt_hints @@ -624,7 +617,7 @@ async fn fetch_and_update_hints( }); drop(cached_excerpt_hints); - if query.should_invalidate() { + if query.invalidate.should_invalidate() { let mut outdated_excerpt_caches = HashSet::default(); for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() { let excerpt_hints = excerpt_hints.read(); @@ -701,7 +694,7 @@ fn calculate_hint_updates( let mut remove_from_visible = Vec::new(); let mut remove_from_cache = HashSet::default(); - if query.should_invalidate() { + if query.invalidate.should_invalidate() { remove_from_visible.extend( visible_hints .iter() @@ -751,7 +744,6 @@ fn calculate_hint_updates( None } else { Some(ExcerptHintsUpdate { - cache_version: query.cache_version, excerpt_id: query.excerpt_id, remove_from_visible, remove_from_cache, @@ -1506,8 +1498,8 @@ mod tests { "Should apply all changes made" ); } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 13); - let expected_hints = vec!["13".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 12); + let expected_hints = vec!["12".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), From 3445bc42b6193a515415aa8004bbdb044fd70671 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 28 Jun 2023 20:58:01 +0300 Subject: [PATCH 165/169] Invalidate refresh tasks better --- crates/editor/src/inlay_hint_cache.rs | 167 ++++++++++++++++---------- 1 file changed, 102 insertions(+), 65 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index e6f5fe03d10b7df05ecab8a2d98272a04eec1511..ffff4ef23144eb28b1c8f4a6c8690884080e26d3 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -9,7 +9,6 @@ use crate::{ }; use anyhow::Context; use clock::Global; -use futures::{future::Shared, FutureExt}; use gpui::{ModelHandle, Task, ViewContext}; use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use log::error; @@ -50,10 +49,15 @@ pub struct InlaySplice { } struct UpdateTask { - invalidation_strategy: InvalidationStrategy, + invalidate: InvalidationStrategy, cache_version: usize, - _task: Shared>, - pending_refresh: Option>, + task: RunningTask, + pending_refresh: Option, +} + +struct RunningTask { + _task: Task<()>, + is_running_rx: smol::channel::Receiver<()>, } #[derive(Debug)] @@ -81,6 +85,11 @@ struct ExcerptDimensions { excerpt_visible_range_end: language::Anchor, } +struct HintFetchRanges { + visible_range: Range, + other_ranges: Vec>, +} + impl InvalidationStrategy { fn should_invalidate(&self) -> bool { matches!( @@ -405,37 +414,29 @@ fn spawn_new_update_tasks( match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { hash_map::Entry::Occupied(mut o) => { let update_task = o.get_mut(); - match (update_task.invalidation_strategy, invalidate) { + match (update_task.invalidate, invalidate) { (_, InvalidationStrategy::None) => {} - (InvalidationStrategy::RefreshRequested, _) - | (_, InvalidationStrategy::ExcerptEdited) - | ( - InvalidationStrategy::None, + ( + InvalidationStrategy::ExcerptEdited, InvalidationStrategy::RefreshRequested, - ) => { + ) if !update_task.task.is_running_rx.is_closed() => { + update_task.pending_refresh = Some(query); + } + _ => { o.insert(UpdateTask { - invalidation_strategy: invalidate, + invalidate, cache_version: query.cache_version, - _task: new_update_task(false).shared(), + task: new_update_task(false), pending_refresh: None, }); } - (_, InvalidationStrategy::RefreshRequested) => { - let pending_fetch = o.get()._task.clone(); - let refresh_task = new_update_task(true); - o.get_mut().pending_refresh = - Some(cx.background().spawn(async move { - pending_fetch.await; - refresh_task.await - })); - } } } hash_map::Entry::Vacant(v) => { v.insert(UpdateTask { - invalidation_strategy: invalidate, + invalidate, cache_version: query.cache_version, - _task: new_update_task(false).shared(), + task: new_update_task(false), pending_refresh: None, }); } @@ -453,9 +454,11 @@ fn new_update_task( cached_excerpt_hints: Option>>, is_refresh_after_regular_task: bool, cx: &mut ViewContext<'_, '_, Editor>, -) -> Task<()> { +) -> RunningTask { let hints_fetch_ranges = query.hints_fetch_ranges(&buffer_snapshot); - cx.spawn(|editor, cx| async move { + let (is_running_tx, is_running_rx) = smol::channel::bounded(1); + let _task = cx.spawn(|editor, mut cx| async move { + let _is_running_tx = is_running_tx; let create_update_task = |range| { fetch_and_update_hints( editor.clone(), @@ -508,7 +511,55 @@ fn new_update_task( } } } - }) + + editor + .update(&mut cx, |editor, cx| { + let pending_refresh_query = editor + .inlay_hint_cache + .update_tasks + .get_mut(&query.excerpt_id) + .and_then(|task| task.pending_refresh.take()); + + if let Some(pending_refresh_query) = pending_refresh_query { + let refresh_multi_buffer = editor.buffer().read(cx); + let refresh_multi_buffer_snapshot = refresh_multi_buffer.snapshot(cx); + let refresh_visible_hints = Arc::new(editor.visible_inlay_hints(cx)); + let refresh_cached_excerpt_hints = editor + .inlay_hint_cache + .hints + .get(&pending_refresh_query.excerpt_id) + .map(Arc::clone); + if let Some(buffer) = + refresh_multi_buffer.buffer(pending_refresh_query.buffer_id) + { + drop(refresh_multi_buffer); + editor.inlay_hint_cache.update_tasks.insert( + pending_refresh_query.excerpt_id, + UpdateTask { + invalidate: InvalidationStrategy::RefreshRequested, + cache_version: editor.inlay_hint_cache.version, + task: new_update_task( + pending_refresh_query, + refresh_multi_buffer_snapshot, + buffer.read(cx).snapshot(), + refresh_visible_hints, + refresh_cached_excerpt_hints, + true, + cx, + ), + pending_refresh: None, + }, + ); + } + } + }) + .ok(); + }); + + RunningTask { + _task, + is_running_rx, + } } async fn fetch_and_update_hints( @@ -544,7 +595,7 @@ async fn fetch_and_update_hints( .context("inlay hint fetch task")?; let background_task_buffer_snapshot = buffer_snapshot.clone(); let backround_fetch_range = fetch_range.clone(); - if let Some(new_update) = cx + let new_update = cx .background() .spawn(async move { calculate_hint_updates( @@ -556,13 +607,15 @@ async fn fetch_and_update_hints( &visible_hints, ) }) - .await - { - update_happened = !new_update.add_to_cache.is_empty() - || !new_update.remove_from_cache.is_empty() - || !new_update.remove_from_visible.is_empty(); - editor - .update(&mut cx, |editor, cx| { + .await; + + editor + .update(&mut cx, |editor, cx| { + if let Some(new_update) = new_update { + update_happened = !new_update.add_to_cache.is_empty() + || !new_update.remove_from_cache.is_empty() + || !new_update.remove_from_visible.is_empty(); + let cached_excerpt_hints = editor .inlay_hint_cache .hints @@ -646,9 +699,9 @@ async fn fetch_and_update_hints( if !to_remove.is_empty() || !to_insert.is_empty() { editor.splice_inlay_hints(to_remove, to_insert, cx) } - }) - .ok(); - } + } + }) + .ok(); Ok(update_happened) } @@ -752,11 +805,6 @@ fn calculate_hint_updates( } } -struct HintFetchRanges { - visible_range: Range, - other_ranges: Vec>, -} - fn contains_position( range: &Range, position: language::Anchor, @@ -1246,7 +1294,7 @@ mod tests { visible_hint_labels(editor, cx), ); let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,); + assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds); assert_eq!(inlay_cache.version, edits_made); }); } @@ -1498,19 +1546,19 @@ mod tests { "Should apply all changes made" ); } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 12); - let expected_hints = vec!["12".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 11); + let expected_hints = vec!["11".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), - "Should get hints from the last edit and refresh request only" + "Should get hints from the refresh request" ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); assert_eq!( - inlay_cache.version, 2, - "Should update the cache version once since refresh did not get new hint updates" + inlay_cache.version, 3, + "Last request and refresh after it should bring updates and cache version bump" ); }); @@ -1539,12 +1587,8 @@ mod tests { "Should apply all changes made" ); } - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 6, - "Should query new hints once more, for last edit. All refresh tasks were before this edit hence should be cancelled." - ); - let expected_hints = vec!["6".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 13); + let expected_hints = vec!["13".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), @@ -1553,10 +1597,7 @@ mod tests { assert_eq!(expected_hints, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!( - inlay_cache.version, 3, - "Should update the cache version once due to the new change" - ); + assert_eq!(inlay_cache.version, 5); }); } @@ -1633,13 +1674,9 @@ mod tests { task_lsp_request_ranges.lock().push(params.range); let query_start = params.range.start; - let query_end = params.range.end; let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; Ok(Some(vec![lsp::InlayHint { - position: lsp::Position::new( - (query_end.line - query_start.line) / 2, - (query_end.character - query_start.character) / 2, - ), + position: query_start, label: lsp::InlayHintLabel::String(i.to_string()), kind: None, text_edits: None, @@ -1701,13 +1738,13 @@ mod tests { assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5, "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints"); - let expected_layers = vec!["4".to_string(), "5".to_string()]; + let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()]; assert_eq!(expected_layers, cached_hint_labels(editor), "Should have hints from the new LSP response after edit"); assert_eq!(expected_layers, visible_hint_labels(editor, cx)); let inlay_cache = editor.inlay_hint_cache(); assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 4, "Should update the cache for every LSP response with hints added"); + assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added"); }); } From 652909cdba12f4df3d5b0fca5122d2275993fd76 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 29 Jun 2023 11:21:12 +0300 Subject: [PATCH 166/169] Post-rebase fixes --- crates/collab/src/tests/integration_tests.rs | 12 +++-- crates/editor/src/inlay_hint_cache.rs | 48 +++++++++++--------- crates/editor/src/multi_buffer.rs | 2 +- crates/project/src/project.rs | 32 ++++++------- crates/rpc/proto/zed.proto | 6 +-- styles/src/theme/syntax.ts | 1 - 6 files changed, 55 insertions(+), 46 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 3cf4d9a876094bfd3991e2302d973c57d4944a2e..b20844a065133539ba71ced8a03217b262c4a69b 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7895,6 +7895,14 @@ async fn test_mutual_editor_inlay_hint_cache_update( let workspace_a = client_a.build_workspace(&project_a, cx_a); cx_a.foreground().start_waiting(); + let _buffer_a = project_a + .update(cx_a, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + let fake_language_server = fake_language_servers.next().await.unwrap(); + let next_call_id = Arc::new(AtomicU32::new(0)); let editor_a = workspace_a .update(cx_a, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -7903,9 +7911,6 @@ async fn test_mutual_editor_inlay_hint_cache_update( .unwrap() .downcast::() .unwrap(); - - let fake_language_server = fake_language_servers.next().await.unwrap(); - let next_call_id = Arc::new(AtomicU32::new(0)); fake_language_server .handle_request::(move |params, _| { let task_next_call_id = Arc::clone(&next_call_id); @@ -7938,6 +7943,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( .next() .await .unwrap(); + cx_a.foreground().finish_waiting(); cx_a.foreground().run_until_parked(); diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index ffff4ef23144eb28b1c8f4a6c8690884080e26d3..c85bbddd582400fdc9fb9afef7de5a1275eb5dfb 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -851,7 +851,6 @@ mod tests { }) }); - cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let lsp_request_count = Arc::new(AtomicU32::new(0)); fake_server @@ -890,7 +889,6 @@ mod tests { }) .next() .await; - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); let mut edits_made = 1; @@ -976,7 +974,6 @@ mod tests { }) }); - cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let lsp_request_count = Arc::new(AtomicU32::new(0)); let another_lsp_request_count = Arc::clone(&lsp_request_count); @@ -1025,7 +1022,6 @@ mod tests { }) .next() .await; - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); let mut edits_made = 1; @@ -1311,7 +1307,6 @@ mod tests { }) }); - cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let fake_server = Arc::new(fake_server); let lsp_request_count = Arc::new(AtomicU32::new(0)); @@ -1353,7 +1348,6 @@ mod tests { expected_changes.push(change_after_opening); } - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1444,7 +1438,6 @@ mod tests { }) }); - cx.foreground().start_waiting(); let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; let fake_server = Arc::new(fake_server); let lsp_request_count = Arc::new(AtomicU32::new(0)); @@ -1488,7 +1481,6 @@ mod tests { add_refresh_task(&mut initial_refresh_tasks); let _ = futures::future::join_all(initial_refresh_tasks).await; - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1546,8 +1538,8 @@ mod tests { "Should apply all changes made" ); } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 11); - let expected_hints = vec!["11".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 10); + let expected_hints = vec!["10".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), @@ -1587,8 +1579,8 @@ mod tests { "Should apply all changes made" ); } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 13); - let expected_hints = vec!["13".to_string()]; + assert_eq!(lsp_request_count.load(Ordering::Relaxed), 12); + let expected_hints = vec!["12".to_string()]; assert_eq!( expected_hints, cached_hint_labels(editor), @@ -1641,14 +1633,22 @@ mod tests { .await; let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }) }); + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + cx.foreground().run_until_parked(); cx.foreground().start_waiting(); + let fake_server = fake_servers.next().await.unwrap(); let editor = workspace .update(cx, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -1657,7 +1657,6 @@ mod tests { .unwrap() .downcast::() .unwrap(); - let fake_server = fake_servers.next().await.unwrap(); let lsp_request_ranges = Arc::new(Mutex::new(Vec::new())); let lsp_request_count = Arc::new(AtomicU32::new(0)); let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges); @@ -1689,7 +1688,6 @@ mod tests { }) .next() .await; - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -1947,7 +1945,6 @@ mod tests { .next() .await; - cx.foreground().finish_waiting(); cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { @@ -2135,13 +2132,22 @@ unedited (2nd) buffer should have the same hint"); let project = Project::test(fs, ["/a".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(Arc::new(language))); - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let worktree_id = workspace.update(cx, |workspace, cx| { workspace.project().read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }) }); + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + cx.foreground().run_until_parked(); + cx.foreground().start_waiting(); + let fake_server = fake_servers.next().await.unwrap(); let editor = workspace .update(cx, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), None, true, cx) @@ -2151,8 +2157,6 @@ unedited (2nd) buffer should have the same hint"); .downcast::() .unwrap(); - let fake_server = fake_servers.next().await.unwrap(); - ("/a/main.rs", editor, fake_server) } @@ -2173,12 +2177,12 @@ unedited (2nd) buffer should have the same hint"); } fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { - let mut zz = editor + let mut hints = editor .visible_inlay_hints(cx) .into_iter() .map(|hint| hint.text.to_string()) .collect::>(); - zz.sort(); - zz + hints.sort(); + hints } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c5070363eb7e2c4bc3910b4444c2195a584f8e4d..31af03f768ea549d04a8802623bbc364acd762a4 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2631,7 +2631,7 @@ impl MultiBufferSnapshot { }; } } - panic!("excerpt not found") + panic!("excerpt not found"); } pub fn can_resolve(&self, anchor: &Anchor) -> bool { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0896932e7b35ec9df75c9fb5170a69ae10251c0a..5bb3751043a61070ee79373681a669803144b70c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2827,23 +2827,23 @@ impl Project { }) .detach(); - language_server - .on_request::({ - move |(), mut cx| async move { - let this = this - .upgrade(&cx) - .ok_or_else(|| anyhow!("project dropped"))?; - this.update(&mut cx, |project, cx| { - cx.emit(Event::RefreshInlays); - project.remote_id().map(|project_id| { - project.client.send(proto::RefreshInlayHints { project_id }) - }) + language_server + .on_request::({ + move |(), mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + this.update(&mut cx, |project, cx| { + cx.emit(Event::RefreshInlays); + project.remote_id().map(|project_id| { + project.client.send(proto::RefreshInlayHints { project_id }) }) - .transpose()?; - Ok(()) - } - }) - .detach(); + }) + .transpose()?; + Ok(()) + } + }) + .detach(); let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token.clone(); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 0950098738f09d08b13ef52efa2e402ed0e32de8..a0b98372b10f05248e1c49fc14844b9de61274f9 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -137,9 +137,9 @@ message Envelope { UpdateWorktreeSettings update_worktree_settings = 113; - InlayHints inlay_hints = 114; - InlayHintsResponse inlay_hints_response = 115; - RefreshInlayHints refresh_inlay_hints = 116; + InlayHints inlay_hints = 116; + InlayHintsResponse inlay_hints_response = 117; + RefreshInlayHints refresh_inlay_hints = 118; } } diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index bfd3bd01382ca6a18700aab28363ae583b0520aa..c0d68e418e459687e1b9a73d5e98c25711625125 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -198,7 +198,6 @@ function build_default_syntax(color_scheme: ColorScheme): Syntax { hint: { color: color.hint, weight: font_weights.bold, - // italic: true, }, emphasis: { color: color.emphasis, From b146762f6835b6a088a8435561a8b85340d12b3b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 29 Jun 2023 16:18:45 +0300 Subject: [PATCH 167/169] Remove a flacky test, fix the failing one --- crates/editor/src/inlay_hint_cache.rs | 183 ++------------------------ crates/lsp/src/lsp.rs | 2 +- crates/project/src/project.rs | 17 +-- 3 files changed, 16 insertions(+), 186 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index c85bbddd582400fdc9fb9afef7de5a1275eb5dfb..af7bf3e4c5fffa40d2bd539b699574524df09abe 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -589,7 +589,6 @@ async fn fetch_and_update_hints( .flatten(); let mut update_happened = false; let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) }; - let new_hints = inlay_hints_fetch_task .await .context("inlay hint fetch task")?; @@ -824,7 +823,7 @@ mod tests { ExcerptRange, InlayHintSettings, }; use futures::StreamExt; - use gpui::{TestAppContext, ViewHandle}; + use gpui::{executor::Deterministic, TestAppContext, ViewHandle}; use language::{ language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, }; @@ -1406,7 +1405,7 @@ mod tests { ); } assert_eq!( - lsp_request_count.load(Ordering::Relaxed), + lsp_request_count.load(Ordering::SeqCst), 3, "Should query new hints one more time, for the last edit only" ); @@ -1426,173 +1425,6 @@ mod tests { }); } - #[gpui::test] - async fn test_hint_refresh_request_cancellation(cx: &mut gpui::TestAppContext) { - let allowed_hint_kinds = HashSet::from_iter([None]); - init_test(cx, |settings| { - settings.defaults.inlay_hints = Some(InlayHintSettings { - enabled: true, - show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), - show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), - show_other_hints: allowed_hint_kinds.contains(&None), - }) - }); - - let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; - let fake_server = Arc::new(fake_server); - let lsp_request_count = Arc::new(AtomicU32::new(0)); - let another_lsp_request_count = Arc::clone(&lsp_request_count); - fake_server - .handle_request::(move |params, _| { - let task_lsp_request_count = Arc::clone(&another_lsp_request_count); - async move { - let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; - assert_eq!( - params.text_document.uri, - lsp::Url::from_file_path(file_with_hints).unwrap(), - ); - Ok(Some(vec![lsp::InlayHint { - position: lsp::Position::new(0, i), - label: lsp::InlayHintLabel::String(i.to_string()), - kind: None, - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - }])) - } - }) - .next() - .await; - - let mut initial_refresh_tasks = Vec::new(); - let task_cx = cx.clone(); - let add_refresh_task = |tasks: &mut Vec>| { - let task_fake_server = Arc::clone(&fake_server); - tasks.push(task_cx.foreground().spawn(async move { - task_fake_server - .request::(()) - .await - .expect("inlay refresh request failed"); - })) - }; - add_refresh_task(&mut initial_refresh_tasks); - add_refresh_task(&mut initial_refresh_tasks); - let _ = futures::future::join_all(initial_refresh_tasks).await; - - cx.foreground().run_until_parked(); - - editor.update(cx, |editor, cx| { - assert_eq!( - lsp_request_count.load(Ordering::Relaxed), - 3, - "Should query new hints once for editor opening and 2 times due to 2 refresh requests" - ); - let expected_hints = vec!["3".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get hints from the last refresh landed only" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!( - inlay_cache.version, 1, - "Only one update should be registered in the cache after all cancellations" - ); - }); - - let mut expected_changes = Vec::new(); - let mut edits_and_refreshes = Vec::new(); - add_refresh_task(&mut edits_and_refreshes); - for async_later_change in ["change #1", "change #2", "change #3"] { - expected_changes.push(async_later_change); - let task_editor = editor.clone(); - let mut task_cx = cx.clone(); - let task_fake_server = Arc::clone(&fake_server); - edits_and_refreshes.push(cx.foreground().spawn(async move { - task_fake_server - .request::(()) - .await - .expect("inlay refresh request failed"); - task_editor.update(&mut task_cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input(async_later_change, cx); - }); - task_fake_server - .request::(()) - .await - .expect("inlay refresh request failed"); - })); - } - let _ = futures::future::join_all(edits_and_refreshes).await; - cx.foreground().run_until_parked(); - - editor.update(cx, |editor, cx| { - let current_text = editor.text(cx); - for change in &expected_changes { - assert!( - current_text.contains(change), - "Should apply all changes made" - ); - } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 10); - let expected_hints = vec!["10".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get hints from the refresh request" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!( - inlay_cache.version, 3, - "Last request and refresh after it should bring updates and cache version bump" - ); - }); - - let mut edits_and_refreshes = Vec::new(); - add_refresh_task(&mut edits_and_refreshes); - for async_later_change in ["last change #1", "last change #2", "last change #3"] { - expected_changes.push(async_later_change); - let task_editor = editor.clone(); - let mut task_cx = cx.clone(); - add_refresh_task(&mut edits_and_refreshes); - edits_and_refreshes.push(cx.foreground().spawn(async move { - task_editor.update(&mut task_cx, |editor, cx| { - editor.change_selections(None, cx, |s| s.select_ranges([13..13])); - editor.handle_input(async_later_change, cx); - }); - })); - } - let _ = futures::future::join_all(edits_and_refreshes).await; - cx.foreground().run_until_parked(); - - editor.update(cx, |editor, cx| { - let current_text = editor.text(cx); - for change in &expected_changes { - assert!( - current_text.contains(change), - "Should apply all changes made" - ); - } - assert_eq!(lsp_request_count.load(Ordering::Relaxed), 12); - let expected_hints = vec!["12".to_string()]; - assert_eq!( - expected_hints, - cached_hint_labels(editor), - "Should get hints from the last edit only" - ); - assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - let inlay_cache = editor.inlay_hint_cache(); - assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); - assert_eq!(inlay_cache.version, 5); - }); - } - #[gpui::test] async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); @@ -1689,7 +1521,6 @@ mod tests { .next() .await; cx.foreground().run_until_parked(); - editor.update(cx, |editor, cx| { let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); ranges.sort_by_key(|range| range.start); @@ -1747,7 +1578,10 @@ mod tests { } #[gpui::test] - async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { + async fn test_multiple_excerpts_large_multibuffer( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); init_test(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { @@ -1873,10 +1707,10 @@ mod tests { multibuffer }); - cx.foreground().start_waiting(); + deterministic.run_until_parked(); + cx.foreground().run_until_parked(); let (_, editor) = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); - let editor_edited = Arc::new(AtomicBool::new(false)); let fake_server = fake_servers.next().await.unwrap(); let closure_editor_edited = Arc::clone(&editor_edited); @@ -1944,7 +1778,6 @@ mod tests { }) .next() .await; - cx.foreground().run_until_parked(); editor.update(cx, |editor, cx| { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 4dee0caa393f5f8520c3f13607b1cb7ac6e2fd71..a01f6e8a49ea744cd4c1d699ada7c27474490907 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -716,7 +716,7 @@ impl LanguageServer { .context("failed to deserialize response"), Err(error) => Err(anyhow!("{}", error.message)), }; - let _ = tx.send(response); + _ = tx.send(response); }) .detach(); }), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5bb3751043a61070ee79373681a669803144b70c..bbb2064da2c2d0a877168ed39d0fe2ad848eba91 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2707,10 +2707,11 @@ impl Project { cx: &mut AsyncAppContext, ) -> Result>> { let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = match pending_server.task.await? { Some(server) => server.initialize(initialization_options).await?, - None => return Ok(None), + None => { + return Ok(None); + } }; language_server @@ -7505,15 +7506,11 @@ impl Project { ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() - .filter_map(|server_id| { - if let LanguageServerState::Running { + .filter_map(|server_id| match self.language_servers.get(&server_id)? { + LanguageServerState::Running { adapter, server, .. - } = self.language_servers.get(&server_id)? - { - Some((adapter, server)) - } else { - None - } + } => Some((adapter, server)), + _ => None, }) } From 53666311733e591caf5b712ff10954fd40ea8c87 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 29 Jun 2023 17:10:51 -0700 Subject: [PATCH 168/169] Remove on_click_out handler from context menu Add 'delay_cancel()' method and on_down handler to relevant buttons --- crates/collab_ui/src/collab_titlebar_item.rs | 5 +- crates/context_menu/src/context_menu.rs | 58 ++++++++++++++++---- crates/copilot_button/src/copilot_button.rs | 7 ++- crates/terminal_view/src/terminal_element.rs | 21 +++---- crates/terminal_view/src/terminal_panel.rs | 2 + crates/workspace/src/pane.rs | 13 +++-- 6 files changed, 79 insertions(+), 27 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 2ab89281660b5750eb4028ce7a2ed87ccc46851a..5caebb9f0c033d9fda6aed87d155f4e1e5b9c655 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -317,7 +317,7 @@ impl CollabTitlebarItem { ), ] }; - user_menu.show(Default::default(), AnchorCorner::TopRight, items, cx); + user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx); }); } @@ -683,6 +683,9 @@ impl CollabTitlebarItem { .into_any() }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, move |_, this, cx| { + this.user_menu.update(cx, |menu, _| menu.delay_cancel()); + }) .on_click(MouseButton::Left, move |_, this, cx| { this.toggle_user_menu(&Default::default(), cx) }) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index a603b3578adb7aa86e657a327b35a4c2c40128cc..296f6bc04a35a72767b635f0047502d62a4556e5 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -124,6 +124,7 @@ pub struct ContextMenu { items: Vec, selected_index: Option, visible: bool, + delay_cancel: bool, previously_focused_view_id: Option, parent_view_id: usize, _actions_observation: Subscription, @@ -178,6 +179,7 @@ impl ContextMenu { pub fn new(parent_view_id: usize, cx: &mut ViewContext) -> Self { Self { show_count: 0, + delay_cancel: false, anchor_position: Default::default(), anchor_corner: AnchorCorner::TopLeft, position_mode: OverlayPositionMode::Window, @@ -232,15 +234,23 @@ impl ContextMenu { } } + pub fn delay_cancel(&mut self) { + self.delay_cancel = true; + } + fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - self.reset(cx); - let show_count = self.show_count; - cx.defer(move |this, cx| { - if cx.handle().is_focused(cx) && this.show_count == show_count { - let window_id = cx.window_id(); - (**cx).focus(window_id, this.previously_focused_view_id.take()); - } - }); + if !self.delay_cancel { + self.reset(cx); + let show_count = self.show_count; + cx.defer(move |this, cx| { + if cx.handle().is_focused(cx) && this.show_count == show_count { + let window_id = cx.window_id(); + (**cx).focus(window_id, this.previously_focused_view_id.take()); + } + }); + } else { + self.delay_cancel = false; + } } fn reset(&mut self, cx: &mut ViewContext) { @@ -293,6 +303,34 @@ impl ContextMenu { } } + pub fn toggle( + &mut self, + anchor_position: Vector2F, + anchor_corner: AnchorCorner, + items: Vec, + cx: &mut ViewContext, + ) { + if self.visible() { + self.cancel(&Cancel, cx); + } else { + let mut items = items.into_iter().peekable(); + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !cx.is_self_focused() { + self.previously_focused_view_id = cx.focused_view_id(); + } + cx.focus_self(); + } else { + self.visible = false; + } + } + cx.notify(); + } + pub fn show( &mut self, anchor_position: Vector2F, @@ -477,10 +515,10 @@ impl ContextMenu { .contained() .with_style(style.container) }) - .on_click_out(MouseButton::Left, |_, this, cx| { + .on_down_out(MouseButton::Left, |_, this, cx| { this.cancel(&Default::default(), cx); }) - .on_click_out(MouseButton::Right, |_, this, cx| { + .on_down_out(MouseButton::Right, |_, this, cx| { this.cancel(&Default::default(), cx); }) } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 9b0581492f470a7d97988dc2c90b48ac63ddbc6d..5576451b1b0f1ac883ce0497f8df35a4fbcd245a 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -102,6 +102,9 @@ impl View for CopilotButton { } }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, |_, this, cx| { + this.popup_menu.update(cx, |menu, _| menu.delay_cancel()); + }) .on_click(MouseButton::Left, { let status = status.clone(); move |_, this, cx| match status { @@ -186,7 +189,7 @@ impl CopilotButton { })); self.popup_menu.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::BottomRight, menu_options, @@ -266,7 +269,7 @@ impl CopilotButton { menu_options.push(ContextMenuItem::action("Sign Out", SignOut)); self.popup_menu.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::BottomRight, menu_options, diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 2f2ff2cdc3bde8bb5ee1c4a32978ed28518bd94f..b92059f5d605bc16c6578b254ce0431113cce200 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -395,16 +395,17 @@ impl TerminalElement { // Terminal Emulator controlled behavior: region = region // Start selections - .on_down( - MouseButton::Left, - TerminalElement::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) + .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| { + cx.focus_parent(); + v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + if let Some(conn_handle) = connection.upgrade(cx) { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_down(&event, origin); + + cx.notify(); + }) + } + }) // Update drag selections .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { if cx.is_self_focused() { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 6de6527a2617fae85ba1b1347cd9c3542c863994..11f8f7abde0509d970ebd65a972246e40ba2d05d 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -87,6 +87,7 @@ impl TerminalPanel { } }) }, + |_, _| {}, None, )) .with_child(Pane::render_tab_bar_button( @@ -100,6 +101,7 @@ impl TerminalPanel { Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + |_, _| {}, None, )) .into_any() diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 9776fede2c3fb76c1639ba083ff9d3b0b6d06dca..e61e60d68f1babe60d9f826144b571666e6d8a77 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -273,6 +273,7 @@ impl Pane { Some(("New...".into(), None)), cx, |pane, cx| pane.deploy_new_menu(cx), + |pane, cx| pane.tab_bar_context_menu.handle.update(cx, |menu, _| menu.delay_cancel()), pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::New), )) @@ -283,6 +284,7 @@ impl Pane { Some(("Split Pane".into(), None)), cx, |pane, cx| pane.deploy_split_menu(cx), + |pane, cx| pane.tab_bar_context_menu.handle.update(cx, |menu, _| menu.delay_cancel()), pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::Split), )) @@ -304,6 +306,7 @@ impl Pane { Some((tooltip_label, Some(Box::new(ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + move |_, _| {}, None, ) }) @@ -988,7 +991,7 @@ impl Pane { fn deploy_split_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::TopRight, vec![ @@ -1006,7 +1009,7 @@ impl Pane { fn deploy_new_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::TopRight, vec![ @@ -1416,13 +1419,14 @@ impl Pane { .into_any() } - pub fn render_tab_bar_button)>( + pub fn render_tab_bar_button), F2: 'static + Fn(&mut Pane, &mut EventContext)>( index: usize, icon: &'static str, is_active: bool, tooltip: Option<(String, Option>)>, cx: &mut ViewContext, - on_click: F, + on_click: F1, + on_down: F2, context_menu: Option>, ) -> AnyElement { enum TabBarButton {} @@ -1440,6 +1444,7 @@ impl Pane { .with_height(style.button_width) }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) .into_any(); if let Some((tooltip, action)) = tooltip { From 73b0f3b23d126a9f06eec8cbf3fbcd15a4c3745f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 29 Jun 2023 17:19:35 -0700 Subject: [PATCH 169/169] fmt --- crates/workspace/src/pane.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e61e60d68f1babe60d9f826144b571666e6d8a77..6a20fab9a275571bd52a55b804e7d6b6407016e6 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -273,7 +273,11 @@ impl Pane { Some(("New...".into(), None)), cx, |pane, cx| pane.deploy_new_menu(cx), - |pane, cx| pane.tab_bar_context_menu.handle.update(cx, |menu, _| menu.delay_cancel()), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::New), )) @@ -284,7 +288,11 @@ impl Pane { Some(("Split Pane".into(), None)), cx, |pane, cx| pane.deploy_split_menu(cx), - |pane, cx| pane.tab_bar_context_menu.handle.update(cx, |menu, _| menu.delay_cancel()), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::Split), )) @@ -1419,7 +1427,10 @@ impl Pane { .into_any() } - pub fn render_tab_bar_button), F2: 'static + Fn(&mut Pane, &mut EventContext)>( + pub fn render_tab_bar_button< + F1: 'static + Fn(&mut Pane, &mut EventContext), + F2: 'static + Fn(&mut Pane, &mut EventContext), + >( index: usize, icon: &'static str, is_active: bool,