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 | 639 +++++++++++++++++--------------- 3 files changed, 351 insertions(+), 291 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e91d5770cf..fa85d0e21f 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 96d4382075..f05acbb1a2 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 5069c805b7..f78ff19eae 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| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| { - cx.emit(Event::LanguageServerLog(server_id, params.message)) - }); - } - } - }) - .detach(); + this.update(&mut cx, |this, cx| { + this.insert_newly_running_language_server( + language, + adapter, + language_server.clone(), + server_id, + key, + cx, + ) + })?; - 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) { - this.update(&mut cx, |this, cx| { - this.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); - }); - } - }) - .detach(); - } - }) - .detach(); + Ok(language_server) + } - 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(); + 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; - // 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(); + let language_server = pending_server.task.await?; + let language_server = language_server.initialize(initialization_options).await?; - 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(); - - 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(); - - let language_server = language_server - .initialize(initialization_options) - .await - .log_err()?; - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, - ) - .ok(); - - 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; - } - - // 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(), - }, - ); - - cx.emit(Event::LanguageServerAdded(server_id)); - - 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(); - } - - // 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, - }; - - if file.worktree.read(cx).id() != key.0 - || !language.lsp_adapters().iter().any(|a| a.name == key.1) - { - continue; - } - - 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(), - ), - }, - ) - .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, - ) + 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)) }); } } - - cx.notify(); - Some(language_server) }) - })) + .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, |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(); + + // 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(); + + 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 + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); + + Ok(language_server) + } + + 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 + 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, + }, + ); + + 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(), + }, + ); + + 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; + } + + let file = match file.as_local() { + Some(file) => file, + None => 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 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(), + ), + }, + )?; + + 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, + ) + }); + } + } + + 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 | 273 +++++++++++------- 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, 347 insertions(+), 155 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 801c8f7172..a8874e8d9e 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 e73424f0cd..b72f00f361 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 fa85d0e21f..2ed896e03f 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 f05acbb1a2..94a5096c5f 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 f78ff19eae..5c9b79434a 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.clone(), language.clone(), server), - ( - adapter, - language, - worktree_id, - worktree_abs_path, - lsp_symbols, - ) - }), - ); - } - } + _ => 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 71ffacebf3..c8beb86aef 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 7e4ddcef19..bfb25f55a0 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 2939a0fa5f..8d6ece28c7 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 ed24abb45c..9ffd88e53a 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 68f780c3af..3d5adb88ba 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 e1f3da9e02..f6ca3f403f 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 9b82713d08..4ff4816ae3 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 f204eb2555..bb9070c1a2 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 7aaddf5fe8..6dd17660c4 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 d387f815f0..189641bec5 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 15700ec80a..67a38d1355 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 7d2d580857..dd2ac22348 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 7f87a7caed..48ce76721d 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 6d24728a0a..fe9682b969 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 2ed896e03f..28cd1cd5b1 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; - } + ) -> Task<()> { + let mut lock = self.lsp_binary_paths.lock(); + lock.remove(&adapter.name); - // If not, get check binary - let test_binary = match language_server.test_installation_binary() { - Some(test_binary) => test_binary.clone(), - None => return, - }; + let download_dir = self + .language_server_download_dir + .clone() + .expect("language server download directory has not been assigned before deleting server container"); - // 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 + 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 5c9b79434a..e131bbb746 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 bfb25f55a0..9d4e1802d5 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 b72f00f361..866bb8d7f5 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 28cd1cd5b1..c25027f9bc 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 94a5096c5f..f7d924604b 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 e131bbb746..9641ecc0f1 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 9d4e1802d5..0f580f1d4f 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 | 159 ++++++++++++++++++-------------- 2 files changed, 90 insertions(+), 71 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c25027f9bc..1d595d0e24 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 9641ecc0f1..41a9d65069 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,74 +2394,89 @@ 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, - }; - - 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; - - 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); + ); } } + 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 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 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 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 LSP ERROR + None + } + } + }) + }); + + self.language_servers.insert(server_id, state); + self.language_server_ids.insert(key, server_id); + } + fn reinstall_language_server( &mut self, server_id: LanguageServerId, @@ -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 | 99 ++++++++++++++++++++------------- crates/lsp/src/lsp.rs | 10 ++-- crates/project/src/project.rs | 94 ++++++++++++++++++++----------- 3 files changed, 125 insertions(+), 78 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 1d595d0e24..e2c7b99763 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 task = { + let container_dir = container_dir.clone(); + 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 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 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 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 f7d924604b..cd7c1a0355 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 41a9d65069..44e1523bd1 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 44e1523bd1..5a42399040 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 5a42399040..490d9fff80 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 b9c2b52f98..e0ff625444 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 e0ce91e772..2536c94225 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 d52de667d4..e973a77f4f 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 8435de71e2..82e2c0c5a5 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 2536c94225..5c22a14d4d 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 e973a77f4f..d52de667d4 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 82e2c0c5a5..8435de71e2 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 5c22a14d4d..2536c94225 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 d52de667d4..deab1d9cc0 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 2536c94225..46c8690555 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 46c8690555..60fd95e007 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 fe9682b969..6d24728a0a 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 a8874e8d9e..9172b84f3c 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 e0ff625444..9e3d3708ba 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 60fd95e007..a33a47779c 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 866bb8d7f5..4c45bf823b 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 9e3d3708ba..beec63dfd2 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 deab1d9cc0..ffca2f24ab 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 a33a47779c..4c6b25b0e9 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 beec63dfd2..4f03da1afe 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 4c6b25b0e9..7be57e921d 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 4d3f08ff53..afac5abeb0 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 4f03da1afe..e8450344b8 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 7be57e921d..0820eaf26e 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 afac5abeb0..c32927e15c 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 9821dcd1eb..d7982f7bdb 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 5e6f956b64..ecc839fca6 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 f40e35ebd7..b7e4ab4ba7 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 3daabb64d0..7c5c7179d0 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 5be05bea2e..41ad28ba86 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 f88f4af5fd..358441352a 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 0a2c93f422..97549b0058 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 c1df52c161..e6d1466731 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 c03bcf405e..b57c6f5699 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 0820eaf26e..561235d35f 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 de78b51e9c..9bdf146da4 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 cffce6c3a6..cc6778c930 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 cf0a08f33e..a492da771b 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 0efc794148..7e1d5d6e1e 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 | 2729 +++++++++++++++++++++++++- 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, 2858 insertions(+), 39 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 0000000000..40800d315c --- /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 236e571148..e5a761b2a2 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,6 +971,24 @@ "node": ">=0.10" } }, + "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", @@ -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,6 +1373,314 @@ "@esbuild/win32-x64": "0.17.19" } }, + "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "@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/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": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "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/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": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "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": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "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": { + "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": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "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": ">=0.10.0" + } + }, + "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", @@ -679,6 +1698,29 @@ "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", @@ -692,11 +1734,134 @@ "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", @@ -715,6 +1880,39 @@ "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", @@ -723,6 +1921,21 @@ "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", @@ -734,6 +1947,46 @@ "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", @@ -753,6 +2006,18 @@ "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", @@ -771,6 +2036,101 @@ "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", @@ -780,12 +2140,106 @@ "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/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", @@ -800,6 +2254,116 @@ "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": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -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", @@ -1237,11 +3340,117 @@ "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", @@ -1257,6 +3466,147 @@ "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/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": ">=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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/semver": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", @@ -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 b14fb5f527..19a77d4dbf 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 2563e718ff..a58e7e0222 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 8a7eb7a549..5198cb195d 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 0000000000..719fe38203 --- /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 0000000000..e1f55a97e2 --- /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 0000000000..97867c9858 --- /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 0000000000..c4194ca12d --- /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 0000000000..3fa2f0eb25 --- /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 | 201 ++++++++++++++---------- crates/zed/src/languages/typescript.rs | 4 +- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 2 +- 7 files changed, 126 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4b12223e5..55e10ed326 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 4c45bf823b..ce4938ed0d 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 fce0fdfe50..53635f2725 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 e2a8d0d003..27a763e7f8 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?; - - 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) - )); + 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" + )); + } } - Ok(()) + 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) + )); + } + } + + 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 packages: Vec<_> = packages + .into_iter() + .map(|(name, version)| format!("{name}@{version}")) + .collect(); - 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")?; + 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", + ]); - 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) - )); - } + 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 e6d1466731..0a47d365b5 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 dcdf5c1ea5..6421014a4d 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 bcdfe57a46..260c7edf94 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 40800d315c..4cb655c9da 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 e5a761b2a2..b6f14480f5 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 19a77d4dbf..e21840e613 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 8d807d62f3..7d831ee42e 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 b471e6cbda..9a86d1d558 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 311ff6daff..3b3eeba53a 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 719fe38203..6f6bb91e58 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 3fa2f0eb25..851acd4b18 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 11720d8993..b071936392 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 4cb655c9da..c131089e14 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 13a6951a82..93a2bd302a 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 8c1981cf97..cd788c540e 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 03a1f7f5ce..99b8996aef 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 d98e00383f..a843002831 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 30e109df1a..f233f02786 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 5d4b7373c3..101f4d437e 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 3b6f26066b..8db1fa4b2e 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 e45647c3d6..f0720d0588 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 88ae04277e..9194e809ee 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 825c5a389a..450f927535 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 5946bfb82c..6d9f84322d 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 a59284c43a..1064eedd0d 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 1e09f4ff6b..2f82e94c43 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 c53f3ba2ff..71c34207eb 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 c98cbe768f..9b66015dc6 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 f8988f1f3a..9e2f8d0a78 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 c42558059c..6249bfb693 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 5501bd4df2..aaf2740a6e 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 cf0f07dd8c..d2c2152ab4 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 3727c1916c..de4a98df53 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 d05eb1b0c5..c114e17176 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 9a86d1d558..9ecad3ab19 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 a58e7e0222..59968d5949 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 e894db3514..99dc878a79 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 339e2e40cf..6ca53afb18 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 af35a8fef4..ae9512d8ce 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 3c7318a56e..a5bcdc9492 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 2b6858e15b..0000000000 --- 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 d82e5f1cde..4eff06026b 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 1666ce5658..fb896112a9 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 c6ef81c667..bf792ffc7b 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 3b3eeba53a..c64a17b5f6 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 afc2ea4d98..5f5da7e47e 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 998f95a636..0000000000 --- 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 b24f2948cf..0000000000 --- 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 40f7a9154c..0000000000 --- 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 6c0b53c35b..0000000000 --- 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 02002dbe9b..0000000000 --- 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 619b0795c8..0000000000 --- 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 8de65a37eb..0000000000 --- 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 9a81073086..ea3b1b9b29 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 f8c44ba3f9..de1f8ee0d4 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 369fceb070..a35013c1e9 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 18e8c5b97e..37850fe019 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 6f6bb91e58..d6da5f9b71 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 e1f55a97e2..3a017feb28 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 97867c9858..6205b725ef 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 c4194ca12d..08ae683349 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 851acd4b18..99a742124a 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 b5c3b3c519..04fd4d53bb 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 5191064707..38c8a90a9e 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 7a8ec69927..cf68509748 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 0000000000..77238e5b45 --- /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 cd788c540e..5957ae5076 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 39c1adb5e5..4664928d55 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 b8a2272cd3..64a91de7b0 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 a843002831..0000000000 --- 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 0000000000..684614d073 --- /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 f233f02786..7003e0765c 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 101f4d437e..988a1c949b 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 8db1fa4b2e..6f69023d12 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 f0720d0588..239b4b5004 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 9194e809ee..fb6b665a14 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 450f927535..6ae2dc5d20 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 6d9f84322d..0c9d1a47eb 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 1064eedd0d..52795be796 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 2f82e94c43..cd2bbe0584 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 71c34207eb..9bf4605580 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 9b66015dc6..88f3cebc37 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 9e2f8d0a78..68eba19494 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 6249bfb693..9a6d400017 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 aaf2740a6e..417c1faea4 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 d2c2152ab4..f3f5a5a144 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 de4a98df53..c38c689f4f 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 c114e17176..f8b103e2e4 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 9ecad3ab19..c236284fa0 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 59968d5949..718ea20642 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 99dc878a79..207924a99f 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 6ca53afb18..e86afb1c0d 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 ae9512d8ce..fe43934f79 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 5198cb195d..4e3dc18627 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 4eff06026b..992631e036 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 fb896112a9..54daae529e 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 bf792ffc7b..c6e6130f54 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 c64a17b5f6..93f1826740 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 5f5da7e47e..48217e89c7 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 ea3b1b9b29..61b459911b 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 2bf625521c..22287bf669 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 de1f8ee0d4..98a73ef5bf 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 a35013c1e9..5d214abdeb 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 bc53ca802a..84e456a6e7 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 42a69b5a52..10309e0d2e 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 94d05cd827..12f16343e9 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 9bdf146da4..a603b3578a 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 cc6778c930..5b15e62efa 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 6f2762db66..03f481b071 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 7e1d5d6e1e..3576529eec 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 5b15e62efa..23fbb33fe1 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 03f481b071..1b8142d964 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 3576529eec..ca2cc04b9d 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 4664928d55..79891c2477 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 64a91de7b0..477c2515e3 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 99b8996aef..59ccff40f7 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 ead8f1e824..c3cde46d65 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 7003e0765c..fbbfbc4cf1 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 988a1c949b..9198f87299 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 6f69023d12..c0b8e9462f 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 239b4b5004..f68ce06d35 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 fb6b665a14..b3b89b7e42 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 6ae2dc5d20..71467f6584 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 0c9d1a47eb..85c7e7d9f5 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 52795be796..69ab7de496 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 cd2bbe0584..9f80f6f34c 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 9bf4605580..14df1b0bb3 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 88f3cebc37..040c8994be 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 68eba19494..42c4f72a26 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 9a6d400017..2d76da7de8 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 417c1faea4..99bd716c16 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 f3f5a5a144..10f556d121 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 c38c689f4f..ac5c577e21 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 f8b103e2e4..8437ab6c1b 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 c236284fa0..37040613b3 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 718ea20642..bc4ac0b5d7 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 207924a99f..621bf21232 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 e86afb1c0d..fb1c572615 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 fe43934f79..d8323809ed 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 4e3dc18627..e902aa4264 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 a5bcdc9492..dc1d098a3c 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 992631e036..5d0bf89d93 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 54daae529e..a872477f49 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 c6e6130f54..e3cf833ce8 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 93f1826740..5d0bc90a00 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 48217e89c7..2ba0281b17 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 61b459911b..5d62bcffdb 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 d6da5f9b71..0000000000 --- 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 3a017feb28..0000000000 --- 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 6205b725ef..0000000000 --- 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 08ae683349..0000000000 --- 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 99a742124a..0000000000 --- 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 38c8a90a9e..cdd9684752 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 cf68509748..925935ebb5 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 c131089e14..0b4af9dcbc 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 77238e5b45..0000000000 --- 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 06621e8cc1..1b90b81048 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 93a2bd302a..88cefe15c7 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 3ce1688152..132b91e582 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 e1878efed7..4e420d679a 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 5957ae5076..5d7aa6e0ad 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 ee47bcc6bd..6c90de4094 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 c0b8e9462f..6e20486631 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 85c7e7d9f5..2018da6b84 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 69ab7de496..df765e0a4a 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 9f80f6f34c..7b0fc5e4ea 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 14df1b0bb3..4e13826013 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 2d76da7de8..2c4fa21867 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 ac5c577e21..589e120e38 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 8437ab6c1b..6fe8170a3c 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 fb1c572615..d67634d5a8 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 5d214abdeb..1b61849d50 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 37850fe019..6a911ce731 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 69a5bd5575..b8456603ce 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 9123c8879d..e14862f423 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 146305890b..30906078ee 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 88cefe15c7..9cfaafdb75 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 132b91e582..98ab8d2708 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 4e420d679a..09eed6a7b9 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 2018da6b84..4e3f8899e0 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 df765e0a4a..f111225c94 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 4e13826013..95760ce1d0 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 42c4f72a26..28f5b17400 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 99bd716c16..22b526f183 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 d8323809ed..63f0b213a6 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 5d0bf89d93..51e62db822 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 a872477f49..ea890232b5 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 5d0bc90a00..9ae9716f66 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 2ba0281b17..8c60c9d402 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 5d62bcffdb..933c616053 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 98a73ef5bf..118d0c7274 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 1b61849d50..a8bf807ee0 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 176ae83bb7..26462bee6d 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 84e456a6e7..21334fb199 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 52c29bb2ec..18699d21cd 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 ebec67b4c2..faf957b642 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 c1b7a05d47..856cd30043 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 c2ebc424e7..fb67fd2471 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 01cb1d67cb..5e9e5b6927 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 8e32c1f68f..0badf4371e 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 75fcb8e830..adc77e7607 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 7ee7ae4ab1..3e89518c0b 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 e12baf9904..68d2c50876 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 11751367a3..c185d69e43 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 07f4a9b3cb..4414987e22 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 b1c98ddfdf..7fdc3b4eba 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 d960444def..bdda48f6c7 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 74693b24fd..ff287bc80d 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 dd3130cea0..8a9fb989ad 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 c387ac5ae9..d94af30334 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 64edd406a8..2426b05400 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 dbccb96013..abb267f5a4 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 a9c034ed44..455e7795e1 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 edfc518b8e..3f33647daa 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 fbef6683bf..2cb4d04539 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 2e5be61f52..b76ccc5b60 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 7feddacd2b..a12ce08e29 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 bf02385247..aceda0d017 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 d2a69e7ab6..9dd3ea7a61 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 1eb2c91916..9caaee8e34 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 6a911ce731..2fa6b58faa 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 4102671189..72757c99f2 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 d550d63768..d8f63ed331 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 05850028a4..0582baa0d8 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 ec3cddec75..bcaea06a41 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 0888e847ac..5eb79f647b 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 6f53ce5299..dc54a33f26 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 75853bc042..72bb100e7b 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 b8456603ce..1241668cc2 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 e14862f423..c3de7826c9 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 30906078ee..5c5482a754 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 15d7b5de2d..c78f1132dd 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 c5ef0c997f..450d6865e7 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 0f3b439338..b305b5b577 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 753828c665..b54c402e47 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 4084757525..05e6f018ab 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 08098d2e2f..f9037feae4 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 cdd9684752..38c8a90a9e 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 79891c2477..4664928d55 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 477c2515e3..64a91de7b0 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 b0cc57875f..0e0013fc07 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 fbbfbc4cf1..1f14d65c8e 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 9198f87299..ca9daad95b 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 f68ce06d35..9f02d450d9 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 b3b89b7e42..93f88e2d4a 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 71467f6584..8de5496d91 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 95760ce1d0..67e67e0cf0 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 040c8994be..ab3a40c148 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 28f5b17400..e9a008b3c6 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 2c4fa21867..91947b9da5 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 22b526f183..7ca76cd85f 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 10f556d121..5962b98a26 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 589e120e38..346ffb7641 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 6fe8170a3c..d93c1d65d5 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 37040613b3..da515114e7 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 bc4ac0b5d7..b57c483f1c 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 621bf21232..896f90a51b 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 d67634d5a8..652c8bf05c 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 63f0b213a6..dc869024bc 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 dc1d098a3c..5972ba4bdd 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 9ae9716f66..2e21e33170 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 8c60c9d402..02b6c80fa9 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 58ee4ccc7c..83c2107483 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 896f90a51b..e9567e235a 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 652c8bf05c..bde40d570c 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 dc869024bc..55fd2c314a 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 5972ba4bdd..067d619bb5 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 51e62db822..df5cc094c1 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 ea890232b5..2fa5db04d4 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 e3cf833ce8..48581a5d21 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 2e21e33170..0d99ad3f77 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 02b6c80fa9..82b05eab91 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 21334fb199..57793cf8dd 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 10309e0d2e..a2e539092e 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 12f16343e9..68a8e676a7 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 2f1760778e..60e846ce94 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 bdd549491f..9422adafa4 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 9f92967a04..47c46d0429 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 6b83ef0582..37a9229268 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 9caaee8e34..2bd0bbf259 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 2a92306143..0e18d6d7a9 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 dc07dc10ad..f7637d33ea 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 dfd60136f9..1276733453 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 c66a06c51b..ba6559d810 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 221eee6f15..2b5ddc4158 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 d7525414ad..dd49a64536 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 9cfaafdb75..76c18dfee1 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 98ab8d2708..5a091719df 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 09eed6a7b9..e33c3712e6 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 6c90de4094..054b283791 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 4664928d55..79891c2477 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 64a91de7b0..477c2515e3 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 1f14d65c8e..bdde221aca 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 ca9daad95b..289deef54b 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 6e20486631..db32712f41 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 9f02d450d9..e61d100264 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 f111225c94..af45942d2d 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 7b0fc5e4ea..eee6880e0c 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 67e67e0cf0..aeb84f678d 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 ab3a40c148..9217b60929 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 e9a008b3c6..f469505741 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 91947b9da5..ca46596e57 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 346ffb7641..6ca37936de 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 d93c1d65d5..ffda0f4b70 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 da515114e7..df260f95b7 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 e9567e235a..0b5c1e0c29 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 df5cc094c1..dc22ac73cf 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 48581a5d21..d14e840450 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 0d99ad3f77..fad8dfe235 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 82b05eab91..0326de414a 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 a8bf807ee0..148d600713 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 57793cf8dd..a8ce4ec2d2 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 68a8e676a7..545a712ff1 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 1241668cc2..f672b892ee 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 38c8a90a9e..cdd9684752 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 0b4af9dcbc..485ff73d10 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 0000000000..b83ccdda6a --- /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 1b90b81048..d82bbb7e81 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 6f3e98a35b..3f73a0b4e5 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 cdd9684752..0000000000 --- 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 478fad74a9..16e706a77e 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 be3bcd05fa..2084b98381 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 6ca37936de..d1024778f1 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 e487b64c4e..592e6c9a53 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 2084b98381..4eb7aed2e5 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 553c5e2cca..f908d702eb 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 4eb7aed2e5..20e693770f 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 8adf98f1bc..038b62f499 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 1293408324..95c7dc5fa9 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 8435de71e2..9e6b3038a3 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 270ee32bab..a7ab9e9068 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 038b62f499..a583bdee0e 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 end = buffer.read(cx).len(); - let inlay_hints_task = project.update(cx, |project, cx| { - project.inlay_hints(buffer.clone(), 0..end, cx) - }); + 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(); - Some(cx.spawn(|_, _| async move { - let inlay_hints = inlay_hints_task.await?; - dbg!(inlay_hints); - Ok(()) - })) + // 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) + }) + }) + })?; + + if let Some(task) = task { + // TODO kb contexts everywhere + let new_hints = task.await?; + inlay_hints_storage.update_if_newer(new_hints, new_timestamp); + } + + 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 6525e7fc22..ee0bd3e8b4 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 95c7dc5fa9..798f35ba5c 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 a7ab9e9068..46da79c5b7 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 a583bdee0e..cc3368f677 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(), - }), - multi_buffer::Event::ExcerptsRemoved { ids } => { - cx.emit(Event::ExcerptsRemoved { ids: ids.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() }); + 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::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); + 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 ee0bd3e8b4..2a58f959d3 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 46da79c5b7..5625efddc2 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 cc3368f677..6db9644e4e 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 2a58f959d3..2cf9ff5fe6 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 798f35ba5c..d8e7efe89b 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 5625efddc2..913c0bbab1 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 | 169 +++++++++++++++++++++--------------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6db9644e4e..23e90f5840 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 + fn read(&self) -> Vec { + self.0.read().1.clone() } - pub fn read(&self) -> Vec { - self.hints.read().clone() + fn is_newer(&self, timestamp: &HashMap) -> bool { + let current_timestamp = self.0.read().0.clone(); + Self::first_timestamp_newer(timestamp, ¤t_timestamp) } - 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 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 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 + .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")?; + + 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); - // 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) - }) - }) - })?; - - if let Some(task) = task { - // TODO kb contexts everywhere - let new_hints = task.await?; + 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); - } - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + }) + .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 a5be6e7d62..583c708e0a 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 23e90f5840..63b3c4f6a3 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 9e6b3038a3..fb01becaf4 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 913c0bbab1..e941eee032 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 2bce1ce1e3..6de98c4595 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 4532e798e7..d917ff10cf 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 dcc2220227..61145e40ff 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 a594af51a6..5dad501df6 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 b20ecaef1c..a010486705 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 0000000000..1388f91d30 --- /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 d97ba4f40b..b54de1ad65 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 dad264d57d..2ae11b3d56 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 editor_addition_point = self + .tab_snapshot + .to_editor_addition_point(tab_point, Bias::Left) + .0; let suggestion_point = self .tab_snapshot - .to_suggestion_point(tab_point, Bias::Left) - .0; + .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 5dad501df6..a870b70f7b 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 1388f91d30..3603400efc 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 b54de1ad65..a3e8cbdb37 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 63b3c4f6a3..bfdfbd2a77 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 fb01becaf4..e735773f4b 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 e941eee032..c33b563ea5 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 3603400efc..63c65e5405 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 bfdfbd2a77..3cc33ac03f 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 2cf9ff5fe6..6525e7fc22 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 a870b70f7b..e34d919164 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 63c65e5405..4f88610f44 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}; -pub struct EditorAdditionMap(Mutex); +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InlayHintId(usize); + +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 e34d919164..ade36990de 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 a010486705..e37c36f5ac 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 4f88610f44..18ac59fcef 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 a3e8cbdb37..4716d62580 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 2ae11b3d56..d0312d2b3a 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 ade36990de..67b3c19d78 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 18ac59fcef..afb4e28de3 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 e735773f4b..3089683cd5 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 c33b563ea5..48d07af78e 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 | 192 ++++++++++----------- crates/project/src/project.rs | 4 +- 5 files changed, 111 insertions(+), 136 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e37c36f5ac..d2c2d11e1d 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 4716d62580..d41b616d62 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 d0312d2b3a..f976fd924b 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 3cc33ac03f..bb4f0e9c3d 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 - }, - ); - - 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:#}"), - } - } - - 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(()) - } + anyhow::Ok(( + buffer_id, + match task_data { + Some((buffer_version, task)) => Some(( + buffer_version, + task.await.context("inlay hints for buffer task")?, + )), + None => None, + }, + )) + }) }) - .detach(); - } + .collect::>(); + + 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:#}"), + } + } + + // 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 48d07af78e..856bf523f7 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 | 277 ++++++++++++++++++--- 2 files changed, 266 insertions(+), 54 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 67b3c19d78..ec76821107 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 afb4e28de3..61d67584fc 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 { +enum Transform { + Isomorphic(TextSummary), + Inlay(Inlay), +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + 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::Item for Transform { - type Summary = TextSummary; +impl sum_tree::Summary for TransformSummary { + type Context = (); - fn summary(&self) -> Self::Summary { - self.output.clone() + 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; + to_remove: HashSet, + to_insert: Vec, + ) -> (InlaySnapshot, Vec, Vec) { + let mut snapshot = self.snapshot.lock(); - // Remove and insert inlays in a single traversal across the tree. - todo!("TODO kb") - } + 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); - 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 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 ec76821107..e8e199b233 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 61d67584fc..a6b6d2f6bc 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 bb4f0e9c3d..b5d963efa4 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 a6b6d2f6bc..8afd6d5fdc 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 eac903d0af..b23f172bca 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 | 65 ++++++++++++++-------- crates/editor/src/editor.rs | 2 + crates/project/src/lsp_command.rs | 1 - 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 8afd6d5fdc..d944074558 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(); - dbg!(self.inlays.len()); + 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, &()), + &(), + ); + } - 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), - }) + if suggestion_edit.old.end > cursor.end(&()) { + cursor.seek_forward(&suggestion_edit.old.end, Bias::Right, &()); + } + + 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), + )), + &(), + ); } - snapshot.suggestion_snapshot = suggestion_snapshot; + new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); - (snapshot.clone(), inlay_edits) + snapshot.suggestion_snapshot = suggestion_snapshot; + dbg!(new_transforms.items(&())); + snapshot.transforms = new_transforms; + + (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 b5d963efa4..ffafef5cb9 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 3089683cd5..85fec37eb8 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 | 48 +++++++++++----------- crates/editor/src/display_map/inlay_map.rs | 2 +- crates/editor/src/editor.rs | 3 +- crates/project/src/project.rs | 1 - 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e8e199b233..30c9b7e69f 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); - // 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); + 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(), + }, + )) + } + } - // 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 d944074558..96ce0af74e 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 ffafef5cb9..9560aa707e 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 856bf523f7..ce42f6df11 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 30c9b7e69f..5644d63c37 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 96ce0af74e..ed58e44432 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 b23f172bca..1b188fe2ed 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 d41b616d62..5c2f05d67c 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 f976fd924b..3f4d1f202f 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 ed58e44432..ceba38388b 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 ceba38388b..529b056eaf 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 529b056eaf..f5c9bbfb4e 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 f5c9bbfb4e..d2fa17dca9 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 d2fa17dca9..66be89b5e4 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 1b188fe2ed..b23f172bca 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 5644d63c37..9a7178e195 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 66be89b5e4..c2b5dcc0d2 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 9560aa707e..b73a02d9c3 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 c2b5dcc0d2..f0367f69df 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 9a7178e195..f7f20e0011 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 f0367f69df..e98ae1b1c9 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 f7f20e0011..17b8733b3a 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 d2c2d11e1d..85d5275dd3 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 e98ae1b1c9..2a780ea6b7 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 3f4d1f202f..a18cc7da56 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 2a780ea6b7..a79bd69786 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 | 63 +++++++++++++--------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index a79bd69786..bf9a466f0d 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); - // Apply the edit. - let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len); - let mut transform_end = suggestion_edit.new.end; + // 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(transform_start) - ..suggestion_snapshot.to_point(transform_end), + 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. + push_isomorphic( + &mut new_snapshot.transforms, + suggestion_snapshot.text_summary_for_range( + 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 bf9a466f0d..e64f740052 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 b73a02d9c3..2d5224de32 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 e64f740052..19010390c9 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 17b8733b3a..82cef10027 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 19010390c9..63edddbf8d 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 0b76dba319..2bfb090bb2 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 82cef10027..240582df8c 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 63edddbf8d..9974c35bd2 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 9a5145c244..b308927cbb 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 fa467886f0..577a942889 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 240582df8c..59ba9d8f4e 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 9974c35bd2..9514e87b4a 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 59ba9d8f4e..40f6ecb76e 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 2d5224de32..ef1db6fcc6 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 }) - .or_default() - .extend(excerpt_hints); + .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) + })); + } } } 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 0000000000..90e5ea01fb --- /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 9514e87b4a..e2308bfcc9 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 ef1db6fcc6..cd12dbefd1 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 90e5ea01fb..fcb89fa913 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 40f6ecb76e..d8924f9692 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 e2308bfcc9..216436bb11 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 cd12dbefd1..fac7d4033b 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 0000000000..e5c48dcba2 --- /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 fcb89fa913..0000000000 --- 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 | 121 +++++++++++++++++++------------ crates/editor/src/inlay_cache.rs | 52 +++++++++---- 2 files changed, 111 insertions(+), 62 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fac7d4033b..9bd9e3daeb 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")?; + .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")?, + 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 e5c48dcba2..d1b14f3342 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 9bd9e3daeb..c1ec82b349 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 c1ec82b349..da2d0950a2 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 85fec37eb8..5df37223c0 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 ce42f6df11..bda93a0206 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 d8924f9692..11f43bea65 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 5df37223c0..bce9bf0e10 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 bda93a0206..2f22201ea4 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 6de98c4595..838a0123c0 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 da2d0950a2..93535c9480 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 d1b14f3342..2f5f4204b9 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 2f22201ea4..5c08ff6b82 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 c69d8089bc..c413db5788 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 93535c9480..afe82bddc5 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 387d4d2c34..557c3194c0 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 2f5f4204b9..c563102544 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 11f43bea65..5d2207d1f9 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 afe82bddc5..22a2c985ae 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 c563102544..71c6f6e337 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 5c08ff6b82..fb2cef9863 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 22a2c985ae..3ab090578d 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 71c6f6e337..d004f07491 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 | 239 +++++++++------------ crates/editor/src/multi_buffer/anchor.rs | 18 ++ 2 files changed, 116 insertions(+), 141 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 216436bb11..cdc06fc66b 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); + // 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(transform_start) + ..suggestion_snapshot.to_point(suggestion_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, }); - push_isomorphic( - &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), - ), - ); - - // 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(&()); - } // 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 b308927cbb..091196e8d0 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 cdc06fc66b..9818ad0aab 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 091196e8d0..1be4dc2dfb 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 9818ad0aab..06bf866ee8 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 06bf866ee8..6a511f3853 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 5d2207d1f9..3a1288248e 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 6a511f3853..5284c0bd80 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 3ab090578d..33bf89e452 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 d004f07491..6cb3895879 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 3a1288248e..f7660ad2b3 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 85d5275dd3..3bb8ccc2ac 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 36cfeba4a1..5e4eeca454 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 5284c0bd80..615d08fa87 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 5c2f05d67c..0deb0f888f 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 a18cc7da56..2e2fa449bd 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 615d08fa87..c3ccaf161e 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 | 656 +++++++------ crates/editor/src/display_map/inlay_map.rs | 550 ++++++----- .../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, 783 insertions(+), 1635 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 f7660ad2b3..cddc10fba7 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 3bb8ccc2ac..a745fddce7 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 5e4eeca454..0cd3817814 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; } + + let next_edit = inlay_edits_iter.next().unwrap(); + 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 = ((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 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); - 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 - } - }) - .peekable(); + 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); - 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; - } - } - - 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, - }, - &(), - ); - } - - 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), - }, - &(), - ); - } + inlay_snapshot.to_inlay_offset(fold_buffer_start) + ..inlay_snapshot.to_inlay_offset(fold_buffer_end) + }); + folds_cursor.next(buffer); + item } + }) + .peekable(); + while folds.peek().map_or(false, |fold| fold.start < edit.new.end) { + let mut fold = folds.next().unwrap(); 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); + + 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; + } + } + + 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); - - 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_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); - - fold_edits.push(FoldEdit { - old: FoldOffset(old_start)..FoldOffset(old_end), - new: FoldOffset(new_start)..FoldOffset(new_end), - }); + 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); - consolidate_fold_edits(&mut fold_edits); + 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(&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_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); + + 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 c3ccaf161e..b9e7119fc9 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.0 += &summary.input.lines; + *self += &summary.input.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *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); + + // 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; + } + + 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), + ); + + if inlay.position.is_valid(&buffer_snapshot) { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + } + } + + // Apply the rest of the edit. + let transform_start = new_transforms.summary().input.len; + push_isomorphic( + &mut new_transforms, + 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, }); - } - } - 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()), &()); + // 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(&()); } } - // 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); + new_transforms.push_tree(cursor.suffix(&()), &()); + if new_transforms.first().is_none() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); + } - // 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 new_snapshot = InlaySnapshot { + buffer: buffer_snapshot, + transforms: new_transforms, + version: post_inc(&mut self.version), }; + new_snapshot.check_invariants(); + drop(cursor); - 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; - } - - let prefix_start = FoldOffset(new_transforms.summary().input.len); - let prefix_end = fold_offset; - push_isomorphic( - &mut new_transforms, - fold_snapshot.text_summary_for_range( - prefix_start.to_point(&fold_snapshot)..prefix_end.to_point(&fold_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 = 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(&()); - } + *buffer = buffer_snapshot.clone(); + (new_snapshot, inlay_edits.into_inner()) } - - 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); - - *snapshot = new_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.fold_snapshot.len(), + None => self.buffer.len(), } } - pub fn to_inlay_point(&self, point: FoldPoint) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); + 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.len(), + } + } + + 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 b23f172bca..0000000000 --- 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 0deb0f888f..9157caace4 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 2e2fa449bd..5197a2e0de 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 33bf89e452..10c86ceb29 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 0cd3817814..741b794902 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 b9e7119fc9..60aa8ea6ee 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 60aa8ea6ee..8bf0226d18 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() { - 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.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() { + 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 | 432 ++++++++++----------- crates/editor/src/display_map/inlay_map.rs | 53 ++- 2 files changed, 239 insertions(+), 246 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 741b794902..3a5bfd37e4 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; + edit.new.end = + InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); - if next_edit.old.end >= edit.old.end { - edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Right, &()); - cursor.next(&()); + 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 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(); + + 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.0 >= 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; + } + } + + 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, + }, + &(), + ); + } + + 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), + }, + &(), + ); } - } else { - break; } - } - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - - 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 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); - - inlay_snapshot.to_inlay_offset(fold_buffer_start) - ..inlay_snapshot.to_inlay_offset(fold_buffer_end) - }); - folds_cursor.next(buffer); - item - } - }) - .peekable(); - - 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!(inlay_snapshot.to_buffer_offset(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; - } - } - - 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), - ); + 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 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; - 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; + 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).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 old_start = - old_transforms.start().1 .0 + (edit.old.start - 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); - - 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_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); - - 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 8bf0226d18..69511300bf 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 a745fddce7..195962d0c2 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 3a5bfd37e4..fb42e25b01 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 69511300bf..ea6acaaffe 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 | 125 ++++++++++++---------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index fb42e25b01..792bba63d0 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)); + // 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 - ); + // 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 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 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 | 126 +++++++++++++--------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 792bba63d0..7e39402e59 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)); + 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 - // ); + 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 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 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 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 | 150 +++++++++++++++++++++++-------- crates/editor/src/inlay_cache.rs | 53 +++++++---- crates/editor/src/scroll.rs | 7 +- 4 files changed, 159 insertions(+), 55 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cddc10fba7..73be476319 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 10c86ceb29..bf6e88de50 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,11 +2641,9 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.fetch_inlays( - buffer_handle, - inlay_fetch_ranges.into_iter(), - cx, - ) + editor + .inlay_cache + .append_inlays(multi_buffer_handle, ranges_to_add, cx) })? .await .context("inlay cache hint fetch")?; @@ -2658,7 +2654,59 @@ impl Editor { }) .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, + ) + })? + .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); + } + }; + } + + 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 6cb3895879..659473f5e6 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 09b75bc680..e0ca2a1ebf 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 7e39402e59..fe3c0d86ab 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 ea6acaaffe..d731e0dc4c 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 bf6e88de50..1e5370cce5 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 659473f5e6..b571a29cc1 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 | 688 ++++++++++----------- 7 files changed, 390 insertions(+), 464 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 73be476319..b0b97fd70d 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 fe3c0d86ab..fb0892b82d 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 d731e0dc4c..3571a603a9 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 9157caace4..021712cd40 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 5197a2e0de..a1f60920cd 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 1e5370cce5..4a40d6ef2f 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 b571a29cc1..7a36208514 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,12 +59,61 @@ 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, + inlays_per_buffer: HashMap::default(), } } + pub fn apply_settings( + &mut self, + 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())); + } + } + + self.allowed_hint_kinds = new_allowed_hint_kinds; + + 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() + } + pub fn append_inlays( &mut self, multi_buffer: ModelHandle, @@ -131,339 +139,289 @@ impl InlayCache { 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 + // TODO kb + todo!("TODO kb") } - 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 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")?; - 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(); + // Ok((inlay_fetch_range, match task { + // Some(task) => task.await.context("inlays for buffer task")?, + // None => Some(Vec::new()), + // })) + // } + // }); + // inlay_fetch_tasks.push(task); + // } - 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 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))?; - let self_inlays_per_buffer = self - .inlays_per_buffer - .get_mut(&buffer_path) - .expect("element expected: `old_inlays.remove` returned `Some`"); + // 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:#}"), + // } + // } - 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; + // 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() + // }; - 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, - } - } + // anyhow::Ok(updates) + // }); - if !retain { - to_remove.push(*old_inlay_id); - } - retain - }, - ); + // final_task + // } - 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)); - } - } + // 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) + // } - 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, - }, - ); - } - } - } + // 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 (_, 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); - } - } - } + // 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; + // } - to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); + // let self_inlays_per_buffer = self + // .inlays_per_buffer + // .get_mut(&buffer_path) + // .expect("element expected: `old_inlays.remove` returned `Some`"); - InlaySplice { - to_remove, - to_insert, - } - } + // 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; - pub fn apply_settings( - &mut self, - inlay_hint_settings: editor_settings::InlayHints, - ) -> InlaySplice { - let new_allowed_inlay_hint_types = allowed_inlay_hint_types(inlay_hint_settings); + // 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, + // } + // } - 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())); - } - } + // if !retain { + // to_remove.push(*old_inlay_id); + // } + // retain + // }, + // ); - self.allowed_hint_kinds = new_allowed_hint_kinds; + // 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)); + // } + // } - InlaySplice { - to_remove, - to_insert, - } - } + // 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, + // }, + // ); + // } + // } + // } - 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() - } + // 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 | 127 +++++++++---------- crates/editor/src/multi_buffer.rs | 16 ++- crates/project/src/project.rs | 5 +- 6 files changed, 247 insertions(+), 159 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index b0b97fd70d..791665bf78 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 3571a603a9..0c7d1bd603 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 4a40d6ef2f..d32dc47154 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, + std::iter::once(updated_range_query), + currently_shown_inlays, + 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, - addition_queries, - 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 7a36208514..7d8dd67e78 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); + 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 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())); + 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 31af03f768..0a70087945 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 fb2cef9863..89975a5746 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 d32dc47154..fc3319dc56 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 7d8dd67e78..4754e2a186 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 0a70087945..d4298efacf 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 e0ca2a1ebf..b5343359b5 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 fc3319dc56..03e98407db 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 4754e2a186..7f3124b5e8 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 791665bf78..16d44fbacf 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 fb0892b82d..fe3c0d86ab 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 0c7d1bd603..43f86287d8 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 021712cd40..9157caace4 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 a1f60920cd..5197a2e0de 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 03e98407db..ed76ee3b29 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 7f3124b5e8..b37545bcdf 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>, } -impl BufferInlays { +#[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 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 b37545bcdf..4da2035af9 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 | 237 ++++++++++++++++++++------ 1 file changed, 181 insertions(+), 56 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 4da2035af9..85ca62840d 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") - // }, - // ); + 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) + } + }); - todo!("TODO kb") + 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 ed76ee3b29..c684821649 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 85ca62840d..72eb241e5a 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 c684821649..dd4b4167a0 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 dd4b4167a0..b834ac6aca 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 6525e7fc22..7936ed76cb 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 72eb241e5a..a89d6da4df 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 b5343359b5..62986b6936 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 | 403 +++++++++----------------- 2 files changed, 149 insertions(+), 263 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b834ac6aca..a7b9d9fb17 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 a89d6da4df..5dc4d04f41 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) } } - - 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 + Err(ix) => Some(ix), }; - if let Some(insert_ix) = insert_ix { - let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id)); + 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 .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 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), - ); } } - 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), - ); + 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.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() + }); + } + 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 | 417 +++++++++----------------- crates/editor/src/scroll.rs | 13 +- 4 files changed, 163 insertions(+), 365 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a7b9d9fb17..8aa5e30742 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 7936ed76cb..6525e7fc22 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 5dc4d04f41..7419a8e484 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) - { - true - } else { - cached_buffer_hints - .hints_per_excerpt - .contains_key(&update_query.excerpt_id) + return None; } - }); - - let queries = filter_queries( - range_updates.into_iter(), - &self.hints_in_buffers, - conflicts_with_cache, - ); + if conflicts_with_cache + || !cached_buffer_hints + .hints_per_excerpt + .contains_key(&query.excerpt_id) + { + Some(query) + } else { + 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(); @@ -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 62986b6936..f623ad03f2 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 43f86287d8..acd26a28f7 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 | 890 ++++++++++++++++++-------- 2 files changed, 650 insertions(+), 325 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8aa5e30742..5be9961e11 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 7419a8e484..548edb1617 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(), - )); - } - } - } - } - - 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, - }) + return; } + + 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), - }; + queries_per_buffer + }, + ); - 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, - }; + 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(); + } + } +} - 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); - } +#[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(()); } } } + } + _ => {} + } - 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)); + 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"), + } + + 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; + } + + 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, + } } - InlaySplice { - to_remove, - to_insert, + 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 5be9961e11..cc4978a5da 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 548edb1617..1058266771 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 f623ad03f2..45ba0bc664 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 | 730 ++++++++++++++------------ 2 files changed, 392 insertions(+), 365 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cc4978a5da..5e8237b832 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 1058266771..0d03787d24 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, - } - } - - 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; - } - updates_limit -= 1; + 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, } - 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; + 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 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; + if updates_limit == 0 { + break 'update_merge; } - 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)), + updates_limit -= 1; + } + Err(smol::channel::TryRecvError::Empty) => break 'update_merge, + Err(smol::channel::TryRecvError::Closed) => return, + } + } + + 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")?; +// 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(); +// 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()) - }); +// 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 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_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, - }; +// 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 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))); - } +// 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() - }); - } +// 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, - }) - }) -} +// 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 | 667 +++++++++++++------------- 2 files changed, 337 insertions(+), 332 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5e8237b832..5c72374ca5 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 0d03787d24..f1080b61c0 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 + } + }; - let InlaySplice { - to_remove, - to_insert, - } = update_result.splice; - editor.splice_inlay_hints(to_remove, to_insert, cx) + 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) + } }) .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 | 275 +++++++++++++------------- 1 file changed, 143 insertions(+), 132 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index f1080b61c0..e294a0717e 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) - { - 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), - ), - } - } - } + 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 } - 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.snapshot.allowed_hint_kinds = + new_allowed_hint_kinds; } - }, - ), - 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, HashMap::default(), remove_from_cache)) } - 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() + { + 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()) }); - !buffer_hints.hints_per_excerpt.is_empty() + 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() }); - 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 acd26a28f7..8639f6d091 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 5c72374ca5..913b3fec66 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 e294a0717e..b6ab31bf1d 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 { + 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, + } + + }, + 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 + } + } + 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 } } - Err(_) => break, - } + break + }, } - _ = &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 - } - } - break - }, } } } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d8e7efe89b..4dee0caa39 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 bce9bf0e10..a7b6e08e91 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 | 1118 ++++++++----------------- crates/editor/src/scroll.rs | 7 +- crates/project/src/project.rs | 4 +- 4 files changed, 395 insertions(+), 825 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 913b3fec66..3ec00c8d8d 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 b6ab31bf1d..08cb84a2b9 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(Clone)] +pub struct HintsUpdateState { + multi_buffer_snapshot: MultiBufferSnapshot, + visible_inlays: Vec, + cache: CacheSnapshot, +} + +#[derive(Debug, Default)] +pub struct InlaySplice { + pub to_remove: Vec, + pub to_insert: 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, -} - -impl BufferHints { - fn new(buffer_version: Global) -> Self { - Self { - buffer_version, - hints_per_excerpt: HashMap::default(), - } - } +struct ExcerptHintsUpdate { + excerpt_id: ExcerptId, + cache_version: usize, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: Vec<(Option, Anchor, 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(); - - 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(); + 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, - ) { - if !inlay_hint_settings.enabled { - self.snapshot.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if self.snapshot.inlay_hints.is_empty() { - return; - } else { - self.hint_updates_tx - .send_blocking(HintsUpdate { - multi_buffer_snapshot, - cache: self.snapshot(), - visible_inlays: current_inlays, - kind: HintsUpdateKind::Clean, - }) - .ok(); - return; - } - } - + update_state: HintsUpdateState, + ) -> Option { let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings); - if new_allowed_hint_kinds == self.snapshot.allowed_hint_kinds { - return; + if !inlay_hint_settings.enabled { + if self.snapshot.hints.is_empty() { + self.snapshot.allowed_hint_kinds = new_allowed_hint_kinds; + } else { + 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; } - 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(); + if new_allowed_hint_kinds == self.snapshot.allowed_hint_kinds { + return None; + } + + 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) - } - }); + 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 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), - )); + 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 + fn clear(&mut self) { + self.snapshot.version += 1; + self.update_tasks.clear(); + self.snapshot.hints.clear(); + self.snapshot.allowed_hint_kinds.clear(); } } -#[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", - } - } -} - -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(), - }) - .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)>, +fn new_update_task( + buffer_id: u64, + excerpt_id: ExcerptId, + cache_version: usize, + state: HintsUpdateState, + invalidate_cache: bool, 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, - } - } +) -> InlayHintUpdateTask { + let hints_fetch_task = hints_fetch_task(buffer_id, excerpt_id, cx); + let task_multi_buffer_snapshot = state.multi_buffer_snapshot.clone(); - 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; + 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 + { + 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, - } - - }, - 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 + + editor.inlay_hint_cache.snapshot.version += 1; + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + 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 } - 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); + 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 + }, + ); - 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, - ); - } + 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, &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, + 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, } } + 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(), - )); - } + 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 45ba0bc664..35ce06ac4d 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 89975a5746..7b868ba54a 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 08cb84a2b9..f5a1309c75 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 f5a1309c75..0eb81aa644 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 0eb81aa644..67dd8d4bd3 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 3ec00c8d8d..93bd073c7a 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 67dd8d4bd3..593cd8c627 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 35ce06ac4d..b0f329c87f 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 7b868ba54a..5da143f602 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 93bd073c7a..bb527f4196 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 | 216 ++++++++++++-------------- 2 files changed, 107 insertions(+), 118 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb527f4196..466600a3fc 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 593cd8c627..a62706bb7f 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); - } - - 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(); + 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, } - 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 add_to_cache: Vec = Vec::new(); + let cached_excerpt_hints = state.cache.hints.get(&excerpt_id); - let mut excerpt_hints_to_persist = HashSet::default(); + 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 a62706bb7f..9044c6f509 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 5da143f602..a248086494 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 9044c6f509..d1e0a1b933 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 466600a3fc..9b944cb7b8 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 d1e0a1b933..b3efbe9e87 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 16d44fbacf..b117120e81 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 9b944cb7b8..288ee516d1 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 b3efbe9e87..019b6e5429 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 288ee516d1..fa23c1be78 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 019b6e5429..b768ffd857 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 583c708e0a..14d785307d 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 3ddef94104..d9644856cd 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 fa23c1be78..753adc16d6 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 b768ffd857..d94a392d53 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 a248086494..a24581a610 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 838a0123c0..0950098738 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 d917ff10cf..605b05a562 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 d9644856cd..35b6f2c100 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 d94a392d53..aa10807b04 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; + } 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 } - - 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; - } - 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 35b6f2c100..d6f9846ab5 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 753adc16d6..dea3655953 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 aa10807b04..d75ecbe888 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 | 509 ++++++++++++------- 3 files changed, 352 insertions(+), 215 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d6f9846ab5..288bc8e7af 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 dea3655953..b5b5d550ce 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 d75ecbe888..0b59029383 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(), - )); - } - - 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(None) => {} - Err(e) => error!( - "Failed to fecth hints for excerpt {:?} in buffer {} : {}", - query.excerpt_id, query.buffer_id, e - ), - } - }), + _task, } } -fn new_excerpt_hints_update_result( +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.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 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 b5b5d550ce..4644336bce 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 0b59029383..6de601f9b2 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; + 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() } - // 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; - } - } + 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 288bc8e7af..5ff7f09ff8 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 4644336bce..5d5bdd1db4 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 6525e7fc22..7936ed76cb 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 b0f329c87f..d595337428 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 195962d0c2..8f78c3885a 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 8639f6d091..4fc0c2fff4 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 9157caace4..eaaaeed8ad 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 5197a2e0de..0e7f1f8167 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 6de601f9b2..d0274b516d 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 b78484e6de..59165283f6 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 577a942889..6ac0ef6350 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 4fc0c2fff4..effd0576ef 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 a7b6e08e91..674ee63349 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 6ac0ef6350..8d219ca021 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 | 59 +++++++++----------- crates/editor/src/inlay_hint_cache.rs | 13 +---- crates/editor/src/multi_buffer.rs | 1 - 5 files changed, 30 insertions(+), 50 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 5ff7f09ff8..8095b39a7f 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 61145e40ff..dcc2220227 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 5d5bdd1db4..65beec6d97 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 d0274b516d..bee08f6cc7 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 d4298efacf..c5070363eb 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 | 353 +++++++++++++------------- 1 file changed, 172 insertions(+), 181 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index bee08f6cc7..b1f9fdf515 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; + }) + .ok() + .flatten(); + let mut update_happened = false; + let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) }; - let mut splice = InlaySplice { - to_remove: new_update.remove_from_visible, - to_insert: Vec::new(), - }; + 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; - 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(), - )); - } + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + to_insert: Vec::new(), + }; - cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); - } + 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); + cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); + } - 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 => {} + 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 7936ed76cb..928df1027f 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 0a62459a3a..e54dcdfd1e 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 0000000000..e69de29bb2 diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts index aeb84f678d..af58276d16 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 148d600713..bfd3bd0138 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 8095b39a7f..bc8f7f4353 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 b1f9fdf515..9c686c7980 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 fe3c0d86ab..7d8eae29ab 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 effd0576ef..7806c75f17 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 b117120e81..e62f715cf3 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 8f78c3885a..4b76ded3d5 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 7d8eae29ab..01b29e1e1a 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 7806c75f17..1b6884dcc6 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 eaaaeed8ad..ca73f6a1a7 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 0e7f1f8167..fe4723abee 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 65beec6d97..e96b3d41c3 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 928df1027f..d1e6f29bbe 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 9c686c7980..d499474a01 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 | 132 ++++++++++--------- crates/language/src/language_settings.rs | 64 ++++++++- crates/project/src/lsp_command.rs | 4 +- crates/project/src/project.rs | 25 +--- 7 files changed, 196 insertions(+), 145 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index bc8f7f4353..905ce6d2e1 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 e96b3d41c3..f70440b922 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 557c3194c0..387d4d2c34 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 d499474a01..1a03886c91 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(); - self.allowed_hint_kinds = new_allowed_hint_kinds; - Some(InlaySplice { - to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), - to_insert: Vec::new(), - }) + ControlFlow::Break(None) } - } 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; + (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; + 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(), + })) + } + } + (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 832bb59222..aeceac9493 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 674ee63349..a3c6302e29 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 a24581a610..0896932e7b 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 01b29e1e1a..0b1523fe75 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 1b6884dcc6..092dbb80b3 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 905ce6d2e1..3cf4d9a876 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 1a03886c91..43fb7ba7cc 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 aeceac9493..820217567a 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 092dbb80b3..affb75f58d 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 fe4723abee..f21c7151ad 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 e62f715cf3..714dc74509 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 | 949 +++++++++++++++++++++++--- 1 file changed, 870 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 43fb7ba7cc..6fdf9b7d27 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,6 +1273,785 @@ mod tests { }); } + #[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.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" + ); + }); + + 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" + ); + }); + } + + #[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); + }); + })); + } + 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" + ); + }); + } + + #[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(); @@ -1335,6 +2068,60 @@ mod tests { 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 { @@ -1346,14 +2133,18 @@ mod tests { } } } + + labels.sort(); labels } fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { - editor + 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 f70440b922..5ccfa0470f 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 6fdf9b7d27..580308f6a6 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 | 204 ++++++++++++++++---------- 1 file changed, 126 insertions(+), 78 deletions(-) diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 580308f6a6..f132a17673 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); - }; + .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, - }; - 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(), - )) + 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 5ccfa0470f..64332c102a 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 f132a17673..e6f5fe03d1 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 e6f5fe03d1..ffff4ef231 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 3cf4d9a876..b20844a065 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 ffff4ef231..c85bbddd58 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 c5070363eb..31af03f768 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 0896932e7b..5bb3751043 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 0950098738..a0b98372b1 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 bfd3bd0138..c0d68e418e 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 c85bbddd58..af7bf3e4c5 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 4dee0caa39..a01f6e8a49 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 5bb3751043..bbb2064da2 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 2ab8928166..5caebb9f0c 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 a603b3578a..296f6bc04a 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 9b0581492f..5576451b1b 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 2f2ff2cdc3..b92059f5d6 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 6de6527a26..11f8f7abde 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 9776fede2c..e61e60d68f 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 e61e60d68f..6a20fab9a2 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,